jsonapi_for_rails 0.1.7 → 0.2.0.pre

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
  SHA1:
3
- metadata.gz: 06227ce867a3b98abeac078d6dd809d9da6791a9
4
- data.tar.gz: ff84fc17fff124f48a269c128f8aece931363b94
3
+ metadata.gz: da54359b12e07d61046ce9a96b2b0c8edadb2af2
4
+ data.tar.gz: bef39399515bd6b7b6a10d5466d26d45c60807ec
5
5
  SHA512:
6
- metadata.gz: 5cb2103dea40467f6ded25d379c2c41a1696258e3630659c225b005f553480c015f07e0beb37149760ffe99422334d21050656d605d3e7b76bcf21a39f6585e9
7
- data.tar.gz: 97c37f8bc2bf07732c0c254f1231594a1ea595202ec0191f42a8eff97c2fc975f6f8e12c1f704b09fe4cb855042e1807ab8ca949af8a0d6a4b8d7a3dc5b5d76b
6
+ metadata.gz: 5e422031d14cea9d91d30591ceafb5616f868b6cd4471c34a705a7ae7837c9aabc4c74acb6627efaa78f7e311b7a6ec640d9b35fe2533ccd419fc1d1d1acf34a
7
+ data.tar.gz: 5750ba36ebe04753855d98914a1fa700f23db4da0541a97d7551b9821c89713a256264f2c9d34fc0400d8a4c67c7f49e47a32d7b187db4e0f933e41692bbe0ee
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -34,7 +34,7 @@ $
34
34
  $ # Check the used version
35
35
  $ bin/rails console
36
36
  irb(main):001:0> JsonapiForRails::VERSION
37
- => "0.1.7"
37
+ => "0.2.0.pre"
38
38
  irb(main):002:0> exit
39
39
  $
40
40
  ```
@@ -70,6 +70,12 @@ class ApplicationController < ActionController::Base # or ActionController::API
70
70
  end
71
71
  ```
72
72
 
73
+ `acts_as_jsonapi_resources` accepts the following keyword arguments:
74
+
75
+ * `content_negotiation`: Set this to `false` to disable
76
+ [content negotiation](http://jsonapi.org/format/1.0/#content-negotiation).
77
+ Default value is `true`. Usage example: `acts_as_jsonapi_resources content_negotiation: false`
78
+
73
79
  If only some of your controllers are JSONAPI controllers, then create a parent controller for only those controllers, and enable JSONAPI inside that controller rather than `ApplicationController`.
74
80
 
75
81
  ```bash
@@ -128,7 +134,7 @@ After populating your database and launching the built-in Rails server with the
128
134
 
129
135
  ```bash
130
136
  $ # Get the list of articles
131
- $ # (the response body is prettified when the Rails environment is 'development' or 'test')
137
+ $ # (the returned HTTP response body is short and terse, but it is prettified here for legibility)
132
138
  $ curl 'http://localhost:3000/api/v1/articles'
133
139
  {
134
140
  "data": [
@@ -142,7 +148,6 @@ $ curl 'http://localhost:3000/api/v1/articles'
142
148
  }
143
149
  ]
144
150
  }
145
- $
146
151
  $ # Get an article
147
152
  $ curl 'http://localhost:3000/api/v1/articles/618037523'
148
153
  {
@@ -165,7 +170,6 @@ $ curl 'http://localhost:3000/api/v1/articles/618037523'
165
170
  }
166
171
  }
167
172
  }
168
- $
169
173
  $ # Get only the title and author of an article, include the author's name
170
174
  $ curl 'http://localhost:3000/api/v1/articles/618037523?filter%5Barticles%5D=title,author;include=author;filter%5Bauthors%5D=name'
171
175
  {
@@ -274,7 +278,8 @@ class ArticlesController < JsonapiResourcesController
274
278
  # @jsonapi_record contains the current database record.
275
279
  # It is available inside all action methods (including all relationship
276
280
  # methods) except :index and :create.
277
- @jsonapi_record.to_jsonapi_hash # => {data: {...}}
281
+ @jsonapi_record.to_jsonapi_hash # => {data: {...}}
282
+ @jsonapi_record.to_jsonapi_errors_hash # => {errors: [...]}
278
283
 
279
284
  # ...
280
285
  end
@@ -305,6 +310,7 @@ The internal architecture is sound. Test coverage is currently being bulked up u
305
310
 
306
311
  Feature support roundup:
307
312
 
313
+ * [Content negotiation](http://jsonapi.org/format/1.0/#content-negotiation) is not implemented.
308
314
  * [Inclusion of related resources](http://jsonapi.org/format/1.0/#fetching-includes) is currently only implemented for requests that return a single resource, and relationship paths are not supported.
309
315
  * [Sparse fieldsets](http://jsonapi.org/format/1.0/#fetching-sparse-fieldsets) is currently only implemented for requests that return a single resource.
310
316
  * [Sorting](http://jsonapi.org/format/1.0/#fetching-sorting) is currently not implemented.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # Rakefile format: http://rake.rubyforge.org/files/doc/rakefile_rdoc.html
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -22,8 +22,9 @@ module JsonapiForRails::Controller
22
22
 
23
23
  # implements Create and Update operations
24
24
  def create
25
+
26
+ # attributes
25
27
  begin
26
- # attributes
27
28
  attrs = received_attributes
28
29
  if attrs
29
30
  if @jsonapi_record
@@ -31,38 +32,42 @@ module JsonapiForRails::Controller
31
32
  @jsonapi_record.update! attrs
32
33
  else
33
34
  # create
34
- @jsonapi_record = jsonapi_model_class.create! attrs
35
- end
36
- end
37
-
38
- # relationships
39
- received_relationships.each do |relationship|
40
- # to-one
41
- if relationship[:definition][:type] == :to_one
42
- @jsonapi_record.send :"#{relationship[:definition][:name]}=", relationship[:params][:data]
43
- next
44
- end
45
-
46
- # to-many
47
- @jsonapi_record.send(relationship[:definition][:name]).send :clear # initialize the relation
48
-
49
- relationship[:params][:data].each do |item|
50
- object = relationship[:receiver][:class].find_by_id item[:id]
51
- @jsonapi_record.send(relationship[:definition][:name]).send :<<, object
35
+ @jsonapi_record = jsonapi_model_class.new attrs
36
+ @jsonapi_record.save!
52
37
  end
53
38
  end
54
39
  rescue NameError => e
55
-
56
- # error when creating record
57
- render_error 500, "Model class not found."
40
+ render_errors 500, "Model class not found."
58
41
  return
59
42
  rescue
60
- # error when creating relationship?
61
- @jsonapi_record.destroy if @jsonapi_record
62
-
63
- render_error 500, "Record could not be created."
43
+ render_errors 500, @jsonapi_record.to_jsonapi_errors_hash
64
44
  return
65
45
  end
46
+
47
+ # relationships
48
+ received_relationships.each do |relationship|
49
+ begin
50
+ # to-one
51
+ if relationship[:definition][:type] == :to_one
52
+ @jsonapi_record.send :"#{relationship[:definition][:name]}=", relationship[:params][:data]
53
+ next
54
+ end
55
+
56
+ # to-many
57
+ @jsonapi_record.send(relationship[:definition][:name]).send :clear # initialize the relation
58
+
59
+ relationship[:params][:data].each do |item|
60
+ object = relationship[:receiver][:class].find_by_id item[:id]
61
+ @jsonapi_record.send(relationship[:definition][:name]).send :<<, object
62
+ end
63
+
64
+ rescue
65
+ # Should not happen
66
+ render_errors 500, "Relationship could not be created."
67
+ return
68
+ end
69
+ end
70
+
66
71
  show
67
72
  end
68
73
 
@@ -108,7 +113,7 @@ module JsonapiForRails::Controller
108
113
  end
109
114
 
110
115
  def destroy
111
- render_error 500, "Not implemented."
116
+ render_errors 500, "Not implemented."
112
117
  end
113
118
 
114
119
  # private
@@ -35,7 +35,7 @@ module JsonapiForRails::Controller
35
35
  # PATCH
36
36
  def relationship_update
37
37
  if @jsonapi_relationship[:definition][:type] == :to_many
38
- render_error 403, 'Replacing all members of a to-many relationship is forbidden.'
38
+ render_errors 403, 'Replacing all members of a to-many relationship is forbidden.'
39
39
  return
40
40
  end
41
41
 
@@ -45,7 +45,7 @@ module JsonapiForRails::Controller
45
45
  @jsonapi_relationship[:params][:data][:id]
46
46
  )
47
47
  unless related
48
- render_error 403, 'Record not found.'
48
+ render_errors 403, 'Record not found.'
49
49
  return
50
50
  end
51
51
  end
@@ -57,7 +57,7 @@ module JsonapiForRails::Controller
57
57
  # POST for to-many relations only
58
58
  def relationship_add
59
59
  unless @jsonapi_relationship[:definition][:type] == :to_many
60
- render_error 403, 'Operation allowed for to-many relationships only.'
60
+ render_errors 403, 'Operation allowed for to-many relationships only.'
61
61
  return
62
62
  end
63
63
 
@@ -66,7 +66,7 @@ module JsonapiForRails::Controller
66
66
  record[:id]
67
67
  )
68
68
  unless record
69
- render_error 403, "Non-existing record #{record.inspect}."
69
+ render_errors 403, "Non-existing record #{record.inspect}."
70
70
  return
71
71
  end
72
72
  record
@@ -80,7 +80,7 @@ module JsonapiForRails::Controller
80
80
  # DELETE for to-many relations only
81
81
  def relationship_remove
82
82
  unless @jsonapi_relationship[:definition][:type] == :to_many
83
- render_error 403, 'Operation allowed for to-many relationships only.'
83
+ render_errors 403, 'Operation allowed for to-many relationships only.'
84
84
  return
85
85
  end
86
86
 
@@ -89,7 +89,7 @@ module JsonapiForRails::Controller
89
89
  record[:id]
90
90
  )
91
91
  unless record
92
- render_error 403, "Non-existing record #{record.inspect}."
92
+ render_errors 403, "Non-existing record #{record.inspect}."
93
93
  return
94
94
  end
95
95
  record
@@ -11,17 +11,62 @@ module JsonapiForRails::Controller
11
11
 
12
12
  def self.run_macros receiver
13
13
  receiver.instance_exec do
14
- before_action :content_negotiation
15
- private :content_negotiation
14
+ before_action :jsonapi_content_negotiation, only: [
15
+ :create,
16
+ :update,
17
+
18
+ :relationship_update,
19
+ :relationship_add,
20
+ :relationship_remove
21
+ ]
22
+
23
+ private :jsonapi_content_negotiation
16
24
  end
17
25
  end
18
26
 
19
27
  module InstanceMethods
20
- def content_negotiation
21
- #$stderr.puts "#{request.headers}"
22
- # TODO: content negotiation
23
- return
24
- render_error 401, "Bad request."
28
+ def jsonapi_content_negotiation
29
+ jsonapi = ::JsonapiForRails::Controller::Utils::Render::JSONAPI
30
+
31
+ # Verify request's Content-Type header #######################
32
+ content_type = request.headers['Content-Type']
33
+ if content_type.nil? or content_type.strip != jsonapi[:content_type]
34
+ # TODO: DEPRECATION WARNING: `:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.
35
+ render status: :unsupported_media_type, nothing: true # 415
36
+ return
37
+ end
38
+
39
+ # Verify request's Accept header #############################
40
+ loop do
41
+ # Request must have Accept header
42
+ accept = request.headers['Accept']
43
+ break unless accept
44
+
45
+ # Accept header's media ranges must match the JSONAPI media type
46
+ acceptable = false
47
+ jsonapi[:media_ranges].each do |media_range|
48
+ index = accept.index media_range
49
+ next unless index
50
+
51
+ acceptable = true
52
+
53
+ # Generic media range match?
54
+ next unless media_range == jsonapi[:content_type]
55
+
56
+ # JSONAPI media type match
57
+ if accept[(index+media_range.size)..-1] =~ /^\s*;/
58
+ # Media type parameter detected
59
+ acceptable = false
60
+ break
61
+ end
62
+ end
63
+ break unless acceptable
64
+
65
+ return
66
+ end
67
+
68
+ # TODO: DEPRECATION WARNING: `:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.
69
+ render :not_acceptable, nothing: true # 406
25
70
  end
26
71
 
27
72
  end
@@ -38,7 +38,7 @@ module JsonapiForRails::Controller
38
38
  end
39
39
  #$stderr.puts "@jsonapi_record: #{@jsonapi_record.inspect}"
40
40
  return if @jsonapi_record
41
- render_error 401, "Bad request."
41
+ render_errors 401, "Bad request."
42
42
  end
43
43
 
44
44
  end
@@ -26,7 +26,7 @@ module JsonapiForRails::Controller
26
26
  @jsonapi_relationship = received_relationships.first
27
27
  return if @jsonapi_relationship
28
28
 
29
- render_error 401, "Bad request."
29
+ render_errors 401, "Bad request."
30
30
  end
31
31
  end
32
32
  end
@@ -11,50 +11,69 @@ module JsonapiForRails::Controller
11
11
 
12
12
  JSONAPI = {
13
13
  specification: 'http://jsonapi.org/format/',
14
- content_type: 'application/vnd.api+json'
14
+ content_type: 'application/vnd.api+json',
15
+ media_range: [
16
+ '*/*',
17
+ 'application/*',
18
+ 'application/vnd.api+json'
19
+ ]
15
20
  }.freeze
16
21
 
17
22
  def self.run_macros receiver
18
23
  receiver.instance_exec do
19
- private :render_json, :render_error
24
+ private :render_json, :render_errors
20
25
  end
21
26
  end
22
27
 
23
28
  module InstanceMethods
24
29
  def render_json object
25
- unless object
26
- render_error 500, 'No message specified.'
27
- return
28
- end
29
-
30
+ # Status code
30
31
  @jsonapi_status = 200
31
- @jsonapi_json = ['development', 'test'].include?(Rails.env) ? JSON.pretty_generate(object) : JSON.generate(object)
32
- @jsonapi_content_type = JSONAPI[:content_type]
33
32
 
33
+ # Generate json
34
+ @jsonapi_json = JSON.generate(object)
35
+
36
+ # Render
34
37
  render(
35
38
  plain: @jsonapi_json,
36
- #json: @jsonapi_json,
37
- status: @jsonapi_status,
38
- content_type: @jsonapi_content_type
39
+ status: @jsonapi_status
39
40
  )
41
+
42
+ # Set content type
43
+ @jsonapi_content_type = JSONAPI[:content_type]
44
+ response.headers['Content-Type'] = @jsonapi_content_type
40
45
  end
41
46
 
42
- def render_error status, title
47
+ def render_errors status, argument
48
+ # Status code
43
49
  @jsonapi_status = status
44
- object = {
45
- errors: [
46
- {title: title}
47
- ]
48
- }
49
- @jsonapi_json = ['development', 'test'].include?(Rails.env) ? JSON.pretty_generate(object) : JSON.generate(object)
50
- @jsonapi_content_type = JSONAPI[:content_type]
51
50
 
51
+ # Generate json
52
+ if argument.kind_of? Hash
53
+ message = argument
54
+ elsif argument.kind_of? Array
55
+ message = {
56
+ errors: argument
57
+ }
58
+ else
59
+ message = {
60
+ errors: [
61
+ {detail: argument.to_s}
62
+ ]
63
+ }
64
+ end
65
+
66
+ @jsonapi_json = JSON.generate(message)
67
+
68
+ # Render
52
69
  render(
53
70
  plain: @jsonapi_json,
54
- #json: @jsonapi_json,
55
- status: @jsonapi_status,
56
- content_type: @jsonapi_content_type
71
+ status: @jsonapi_status
57
72
  )
73
+
74
+ # Set content type
75
+ @jsonapi_content_type = JSONAPI[:content_type]
76
+ response.headers['Content-Type'] = @jsonapi_content_type
58
77
  end
59
78
  end
60
79
 
@@ -17,12 +17,12 @@ module JsonapiForRails::Controller
17
17
  end
18
18
 
19
19
  class_methods do
20
- def acts_as_jsonapi_resources model: nil
20
+ def acts_as_jsonapi_resources content_negotiation: true #, model: nil
21
21
  #$stderr.puts "JsonapiForRails::Controller macro called from #{self}:\n acts_as_jsonapi_resources(model: #{model or 'nil'})"
22
22
 
23
23
  include JsonapiForRails::Controller::Utils::Model
24
24
  include JsonapiForRails::Controller::Utils::Render
25
- include JsonapiForRails::Controller::BeforeActions::ContentNegotiation
25
+ include JsonapiForRails::Controller::BeforeActions::ContentNegotiation if content_negotiation
26
26
  include JsonapiForRails::Controller::BeforeActions::SparseFieldsets
27
27
  include JsonapiForRails::Controller::BeforeActions::Include
28
28
  include JsonapiForRails::Controller::BeforeActions::Record
@@ -25,6 +25,7 @@ module JsonapiForRails::Model
25
25
  if sparse_fieldset
26
26
  next unless sparse_fieldset.find{|f| association.name == f}
27
27
  end
28
+
28
29
  relationship = {}
29
30
  relationships[association.name] = relationship
30
31
 
@@ -47,7 +48,6 @@ module JsonapiForRails::Model
47
48
 
48
49
  # to-one relationship
49
50
  elsif [
50
-
51
51
  ActiveRecord::Reflection::HasOneReflection,
52
52
  ActiveRecord::Reflection::BelongsToReflection
53
53
  ].include? association.class
@@ -66,25 +66,35 @@ module JsonapiForRails::Model
66
66
 
67
67
  # message
68
68
  {
69
- =begin
70
- meta: {
71
- generated_by_class: "#{self.class}"
72
- },
73
- =end
74
69
  data: {
75
- type: jsonapi_model_type,
76
- id: self.id.to_s,
77
-
78
- attributes: attrs,
79
-
70
+ type: jsonapi_model_type,
71
+ id: self.id.to_s,
72
+ attributes: attrs,
80
73
  relationships: relationships
81
74
  }
82
75
  }
83
76
 
84
77
  end
85
78
 
79
+ def to_jsonapi_errors_hash
80
+ {
81
+ errors: errors.messages.to_a.reduce([]){ |result, key_value|
82
+ attribute = key_value.first
83
+ messages = key_value.last
84
+ result + messages.map{ |message|
85
+ {
86
+ detail: message,
87
+ source: {
88
+ pointer: "/data/attributes/#{attribute}",
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ end
95
+
86
96
  def jsonapi_model_type
87
- "#{self.class}".underscore.pluralize.to_sym
97
+ self.class.to_s.underscore.pluralize.to_sym
88
98
  end
89
99
 
90
100
  private :jsonapi_model_type
@@ -1,3 +1,4 @@
1
1
  module JsonapiForRails
2
- VERSION = '0.1.7'
2
+ VERSION = '0.2.0.pre'
3
+ # implement content negotiation (http://jsonapi.org/format/1.0/#content-negotiation), add content_negotiation keyword argument to acts_as_jasonapi_resources
3
4
  end
@@ -1,11 +1,14 @@
1
- # TODO: JSONAPI: adding optional members to documents ('jsonapi', 'meta')
2
- # TODO: JSONAPI: return conformant HTTP status codes
3
- # TODO: JSONAPI: conformant and rich 'errors' member
4
- # TODO: JSONAPI: content negotiation
5
- # TODO: JSONAPI: apply sparse fieldsets _after_ including related resources
6
- # TODO: JSONAPI: should included resources list their relationships (which links to primary object)?
7
- # TODO: JSONAPI: do not support Client-Generated IDs?
8
- # TODO: JSONAPI: Location header
1
+
2
+ # TODO: content negotiation tests
3
+ # TODO: apply sparse fieldsets _after_ including related resources
4
+ # TODO: Location header
5
+ # TODO: add 'links' to documents
6
+ # TODO: return conformant HTTP status codes
7
+ # TODO: options for 'acts_as_jsonapi_resources'? example: {content_negotiation: false}
8
+ # TODO: adding optional members to documents ('jsonapi', 'meta')
9
+ # TODO: conformant and rich 'errors' member
10
+ # TODO: should included resources list their relationships (which links to primary object)?
11
+ # TODO: do not support Client-Generated IDs?
9
12
 
10
13
  # TODO: README.md: double-check the installation instructions
11
14
  # TODO: README.md: describe @jsonapi_include ?
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi_for_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Doga Armangil
@@ -31,7 +31,7 @@ cert_chain:
31
31
  DcNXARPdnPlz0VQedZo89pKjngah8Nur01/wD97Q18Mr/av3H4Bg1sTF+RZAplU3
32
32
  RX+xYPJjPMyjrD73KZ5UNtXo5jOP1T85SqXOl6ICfedTVgZMk9mzK8+PkP1croHi
33
33
  -----END CERTIFICATE-----
34
- date: 2016-03-02 00:00:00.000000000 Z
34
+ date: 2016-03-11 00:00:00.000000000 Z
35
35
  dependencies:
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rails
@@ -95,12 +95,7 @@ files:
95
95
  homepage: https://github.com/doga/jsonapi_for_rails
96
96
  licenses:
97
97
  - MIT
98
- metadata:
99
- code: https://github.com/doga/jsonapi_for_rails
100
- docs: https://github.com/doga/jsonapi_for_rails#jsonapiforrails
101
- wiki: ''
102
- mail: ''
103
- bugs: https://github.com/doga/jsonapi_for_rails/issues
98
+ metadata: {}
104
99
  post_install_message:
105
100
  rdoc_options: []
106
101
  require_paths:
@@ -112,9 +107,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
107
  version: '2.0'
113
108
  required_rubygems_version: !ruby/object:Gem::Requirement
114
109
  requirements:
115
- - - ">="
110
+ - - ">"
116
111
  - !ruby/object:Gem::Version
117
- version: '0'
112
+ version: 1.3.1
118
113
  requirements: []
119
114
  rubyforge_project:
120
115
  rubygems_version: 2.5.1
metadata.gz.sig CHANGED
Binary file