jsonapi-resources 0.5.9 → 0.6.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 +4 -4
- data/.travis.yml +2 -3
- data/README.md +101 -32
- data/jsonapi-resources.gemspec +0 -1
- data/lib/jsonapi/acts_as_resource_controller.rb +9 -9
- data/lib/jsonapi/link_builder.rb +1 -1
- data/lib/jsonapi/operation.rb +1 -1
- data/lib/jsonapi/request.rb +58 -65
- data/lib/jsonapi/resource.rb +8 -15
- data/lib/jsonapi/resource_serializer.rb +9 -4
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/routing_ext.rb +1 -1
- data/test/config/database.yml +1 -2
- data/test/controllers/controller_test.rb +80 -2
- data/test/fixtures/active_record.rb +59 -10
- data/test/fixtures/companies.yml +4 -0
- data/test/integration/routes/routes_test.rb +2 -2
- data/test/integration/sti_fields_test.rb +18 -0
- data/test/test_helper.rb +7 -1
- data/test/unit/resource/resource_test.rb +30 -17
- data/test/unit/serializer/link_builder_test.rb +8 -8
- data/test/unit/serializer/polymorphic_serializer_test.rb +2 -2
- data/test/unit/serializer/serializer_test.rb +16 -16
- metadata +7 -18
data/lib/jsonapi/resource.rb
CHANGED
@@ -22,7 +22,7 @@ module JSONAPI
|
|
22
22
|
:remove_to_one_link,
|
23
23
|
:replace_fields
|
24
24
|
|
25
|
-
def initialize(model, context
|
25
|
+
def initialize(model, context)
|
26
26
|
@model = model
|
27
27
|
@context = context
|
28
28
|
end
|
@@ -111,7 +111,7 @@ module JSONAPI
|
|
111
111
|
|
112
112
|
# Override this on a resource to customize how the associated records
|
113
113
|
# are fetched for a model. Particularly helpful for authorization.
|
114
|
-
def records_for(relation_name
|
114
|
+
def records_for(relation_name)
|
115
115
|
model.public_send relation_name
|
116
116
|
end
|
117
117
|
|
@@ -180,7 +180,6 @@ module JSONAPI
|
|
180
180
|
|
181
181
|
def _replace_to_many_links(relationship_type, relationship_key_values)
|
182
182
|
relationship = self.class._relationships[relationship_type]
|
183
|
-
|
184
183
|
send("#{relationship.foreign_key}=", relationship_key_values)
|
185
184
|
@save_needed = true
|
186
185
|
|
@@ -208,9 +207,9 @@ module JSONAPI
|
|
208
207
|
end
|
209
208
|
|
210
209
|
def _remove_to_many_link(relationship_type, key)
|
211
|
-
|
210
|
+
relation_name = self.class._relationships[relationship_type].relation_name(context: @context)
|
212
211
|
|
213
|
-
@model.public_send(
|
212
|
+
@model.public_send(relation_name).delete(key)
|
214
213
|
|
215
214
|
:completed
|
216
215
|
end
|
@@ -619,11 +618,6 @@ module JSONAPI
|
|
619
618
|
@_relationships.map { |key, _relationship| key }
|
620
619
|
end
|
621
620
|
|
622
|
-
def _has_relationship?(type)
|
623
|
-
type = type.to_s
|
624
|
-
@_relationships.key?(type.singularize.to_sym) || @_relationships.key?(type.pluralize.to_sym)
|
625
|
-
end
|
626
|
-
|
627
621
|
def _relationship(type)
|
628
622
|
type = type.to_sym
|
629
623
|
@_relationships[type]
|
@@ -684,7 +678,7 @@ module JSONAPI
|
|
684
678
|
end
|
685
679
|
|
686
680
|
def module_path
|
687
|
-
@module_path ||= name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').
|
681
|
+
@module_path ||= name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').underscore : ''
|
688
682
|
end
|
689
683
|
|
690
684
|
def construct_order_options(sort_params)
|
@@ -747,10 +741,9 @@ module JSONAPI
|
|
747
741
|
@model.method("#{foreign_key}=").call(value)
|
748
742
|
end unless method_defined?("#{foreign_key}=")
|
749
743
|
|
750
|
-
define_method associated_records_method_name do
|
751
|
-
|
752
|
-
relation_name
|
753
|
-
records_for(relation_name, options)
|
744
|
+
define_method associated_records_method_name do
|
745
|
+
relation_name = relationship.relation_name(context: @context)
|
746
|
+
records_for(relation_name)
|
754
747
|
end unless method_defined?(associated_records_method_name)
|
755
748
|
|
756
749
|
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
@@ -122,12 +122,17 @@ module JSONAPI
|
|
122
122
|
obj_hash
|
123
123
|
end
|
124
124
|
|
125
|
-
def requested_fields(
|
126
|
-
@fields
|
125
|
+
def requested_fields(klass)
|
126
|
+
return if @fields.nil? || @fields.empty?
|
127
|
+
if @fields[klass._type]
|
128
|
+
@fields[klass._type]
|
129
|
+
elsif klass.superclass != JSONAPI::Resource
|
130
|
+
requested_fields(klass.superclass)
|
131
|
+
end
|
127
132
|
end
|
128
133
|
|
129
134
|
def attribute_hash(source)
|
130
|
-
requested = requested_fields(source.class
|
135
|
+
requested = requested_fields(source.class)
|
131
136
|
fields = source.fetchable_fields & source.class._attributes.keys.to_a
|
132
137
|
fields = requested & fields unless requested.nil?
|
133
138
|
|
@@ -141,7 +146,7 @@ module JSONAPI
|
|
141
146
|
|
142
147
|
def relationship_data(source, include_directives)
|
143
148
|
relationships = source.class._relationships
|
144
|
-
requested = requested_fields(source.class
|
149
|
+
requested = requested_fields(source.class)
|
145
150
|
fields = relationships.keys
|
146
151
|
fields = requested & fields unless requested.nil?
|
147
152
|
|
data/lib/jsonapi/routing_ext.rb
CHANGED
@@ -154,7 +154,7 @@ module ActionDispatch
|
|
154
154
|
end
|
155
155
|
|
156
156
|
if methods.include?(:destroy)
|
157
|
-
match "relationships/#{formatted_relationship_name}
|
157
|
+
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
|
158
158
|
action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
|
159
159
|
end
|
160
160
|
end
|
data/test/config/database.yml
CHANGED
@@ -1208,13 +1208,31 @@ class PostsControllerTest < ActionController::TestCase
|
|
1208
1208
|
p = Post.find(14)
|
1209
1209
|
assert_equal [2, 3], p.tag_ids
|
1210
1210
|
|
1211
|
-
delete :destroy_relationship, {post_id: 14, relationship: 'tags',
|
1211
|
+
delete :destroy_relationship, {post_id: 14, relationship: 'tags', data: [{type: 'tags', id: 3}]}
|
1212
1212
|
|
1213
1213
|
p.reload
|
1214
1214
|
assert_response :no_content
|
1215
1215
|
assert_equal [2], p.tag_ids
|
1216
1216
|
end
|
1217
1217
|
|
1218
|
+
def test_delete_relationship_to_many_with_relationship_url_not_matching_type
|
1219
|
+
set_content_type_header!
|
1220
|
+
PostResource.has_many :special_tags, relation_name: :special_tags, class_name: "Tag"
|
1221
|
+
post :create_relationship, {post_id: 14, relationship: 'special_tags', data: [{type: 'tags', id: 2}]}
|
1222
|
+
|
1223
|
+
#check the relationship was created successfully
|
1224
|
+
assert_equal 1, Post.find(14).special_tags.count
|
1225
|
+
before_tags = Post.find(14).tags.count
|
1226
|
+
|
1227
|
+
delete :destroy_relationship, {post_id: 14, relationship: 'special_tags', data: [{type: 'tags', id: 2}]}
|
1228
|
+
assert_equal 0, Post.find(14).special_tags.count, "Relationship that matches URL relationship not destroyed"
|
1229
|
+
|
1230
|
+
#check that the tag association is not affected
|
1231
|
+
assert_equal Post.find(14).tags.count, before_tags
|
1232
|
+
ensure
|
1233
|
+
PostResource.instance_variable_get(:@_relationships).delete(:special_tags)
|
1234
|
+
end
|
1235
|
+
|
1218
1236
|
def test_delete_relationship_to_many_does_not_exist
|
1219
1237
|
set_content_type_header!
|
1220
1238
|
put :update_relationship, {post_id: 14, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 3}]}
|
@@ -1222,7 +1240,7 @@ class PostsControllerTest < ActionController::TestCase
|
|
1222
1240
|
p = Post.find(14)
|
1223
1241
|
assert_equal [2, 3], p.tag_ids
|
1224
1242
|
|
1225
|
-
delete :destroy_relationship, {post_id: 14, relationship: 'tags',
|
1243
|
+
delete :destroy_relationship, {post_id: 14, relationship: 'tags', data: [{type: 'tags', id: 4}]}
|
1226
1244
|
|
1227
1245
|
p.reload
|
1228
1246
|
assert_response :not_found
|
@@ -2827,6 +2845,54 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
|
|
2827
2845
|
assert_response :success
|
2828
2846
|
assert_equal 2, json_response['data'].size
|
2829
2847
|
end
|
2848
|
+
|
2849
|
+
def test_books_create_unapproved_comment_limited_user_using_relation_name
|
2850
|
+
set_content_type_header!
|
2851
|
+
$test_user = Person.find(1)
|
2852
|
+
|
2853
|
+
book_comment = BookComment.create(body: 'Not Approved dummy comment', approved: false)
|
2854
|
+
post :create_relationship, {book_id: 1, relationship: 'book_comments', data: [{type: 'book_comments', id: book_comment.id}]}
|
2855
|
+
|
2856
|
+
# Note the not_found response is coming from the BookComment's overridden records method, not the relation
|
2857
|
+
assert_response :not_found
|
2858
|
+
|
2859
|
+
ensure
|
2860
|
+
book_comment.delete
|
2861
|
+
end
|
2862
|
+
|
2863
|
+
def test_books_create_approved_comment_limited_user_using_relation_name
|
2864
|
+
set_content_type_header!
|
2865
|
+
$test_user = Person.find(1)
|
2866
|
+
|
2867
|
+
book_comment = BookComment.create(body: 'Approved dummy comment', approved: true)
|
2868
|
+
post :create_relationship, {book_id: 1, relationship: 'book_comments', data: [{type: 'book_comments', id: book_comment.id}]}
|
2869
|
+
assert_response :success
|
2870
|
+
|
2871
|
+
ensure
|
2872
|
+
book_comment.delete
|
2873
|
+
end
|
2874
|
+
|
2875
|
+
def test_books_delete_unapproved_comment_limited_user_using_relation_name
|
2876
|
+
$test_user = Person.find(1)
|
2877
|
+
|
2878
|
+
book_comment = BookComment.create(book_id: 1, body: 'Not Approved dummy comment', approved: false)
|
2879
|
+
delete :destroy_relationship, {book_id: 1, relationship: 'book_comments', data: [{type: 'book_comments', id: book_comment.id}]}
|
2880
|
+
assert_response :not_found
|
2881
|
+
|
2882
|
+
ensure
|
2883
|
+
book_comment.delete
|
2884
|
+
end
|
2885
|
+
|
2886
|
+
def test_books_delete_approved_comment_limited_user_using_relation_name
|
2887
|
+
$test_user = Person.find(1)
|
2888
|
+
|
2889
|
+
book_comment = BookComment.create(book_id: 1, body: 'Approved dummy comment', approved: true)
|
2890
|
+
delete :destroy_relationship, {book_id: 1, relationship: 'book_comments', data: [{type: 'book_comments', id: book_comment.id}]}
|
2891
|
+
assert_response :no_content
|
2892
|
+
|
2893
|
+
ensure
|
2894
|
+
book_comment.delete
|
2895
|
+
end
|
2830
2896
|
end
|
2831
2897
|
|
2832
2898
|
class Api::V2::BookCommentsControllerTest < ActionController::TestCase
|
@@ -2971,6 +3037,18 @@ class Api::V1::MoonsControllerTest < ActionController::TestCase
|
|
2971
3037
|
}
|
2972
3038
|
|
2973
3039
|
end
|
3040
|
+
|
3041
|
+
def test_get_related_resources_with_select_some_db_columns
|
3042
|
+
PlanetResource.paginator :paged
|
3043
|
+
original_config = JSONAPI.configuration.dup
|
3044
|
+
JSONAPI.configuration.top_level_meta_include_record_count = true
|
3045
|
+
JSONAPI.configuration.json_key_format = :dasherized_key
|
3046
|
+
get :get_related_resources, {planet_id: '1', relationship: 'moons', source: 'api/v1/planets'}
|
3047
|
+
assert_response :success
|
3048
|
+
assert_equal 1, json_response['meta']['record-count']
|
3049
|
+
ensure
|
3050
|
+
JSONAPI.configuration = original_config
|
3051
|
+
end
|
2974
3052
|
end
|
2975
3053
|
|
2976
3054
|
class Api::V1::CratersControllerTest < ActionController::TestCase
|
@@ -38,6 +38,13 @@ ActiveRecord::Schema.define do
|
|
38
38
|
t.timestamps null: false
|
39
39
|
end
|
40
40
|
|
41
|
+
create_table :companies, force: true do |t|
|
42
|
+
t.string :type
|
43
|
+
t.string :name
|
44
|
+
t.string :address
|
45
|
+
t.timestamps null: false
|
46
|
+
end
|
47
|
+
|
41
48
|
create_table :tags, force: true do |t|
|
42
49
|
t.string :name
|
43
50
|
end
|
@@ -51,6 +58,11 @@ ActiveRecord::Schema.define do
|
|
51
58
|
end
|
52
59
|
add_index :posts_tags, [:post_id, :tag_id], unique: true
|
53
60
|
|
61
|
+
create_table :special_post_tags, force: true do |t|
|
62
|
+
t.references :post, :tag, index: true
|
63
|
+
end
|
64
|
+
add_index :special_post_tags, [:post_id, :tag_id], unique: true
|
65
|
+
|
54
66
|
create_table :comments_tags, force: true do |t|
|
55
67
|
t.references :comment, :tag, index: true
|
56
68
|
end
|
@@ -234,18 +246,31 @@ class Post < ActiveRecord::Base
|
|
234
246
|
belongs_to :writer, class_name: 'Person', foreign_key: 'author_id'
|
235
247
|
has_many :comments
|
236
248
|
has_and_belongs_to_many :tags, join_table: :posts_tags
|
249
|
+
has_many :special_post_tags, source: :tag
|
250
|
+
has_many :special_tags, through: :special_post_tags, source: :tag
|
237
251
|
belongs_to :section
|
238
252
|
|
239
253
|
validates :author, presence: true
|
240
254
|
validates :title, length: { maximum: 35 }
|
241
255
|
end
|
242
256
|
|
257
|
+
class SpecialPostTag < ActiveRecord::Base
|
258
|
+
belongs_to :tag
|
259
|
+
belongs_to :post
|
260
|
+
end
|
261
|
+
|
243
262
|
class Comment < ActiveRecord::Base
|
244
263
|
belongs_to :author, class_name: 'Person', foreign_key: 'author_id'
|
245
264
|
belongs_to :post
|
246
265
|
has_and_belongs_to_many :tags, join_table: :comments_tags
|
247
266
|
end
|
248
267
|
|
268
|
+
class Company < ActiveRecord::Base
|
269
|
+
end
|
270
|
+
|
271
|
+
class Firm < Company
|
272
|
+
end
|
273
|
+
|
249
274
|
class Tag < ActiveRecord::Base
|
250
275
|
has_and_belongs_to_many :posts, join_table: :posts_tags
|
251
276
|
has_and_belongs_to_many :planets, join_table: :planets_tags
|
@@ -366,6 +391,15 @@ end
|
|
366
391
|
class BookComment < ActiveRecord::Base
|
367
392
|
belongs_to :author, class_name: 'Person', foreign_key: 'author_id'
|
368
393
|
belongs_to :book
|
394
|
+
|
395
|
+
def self.for_user(current_user)
|
396
|
+
records = self
|
397
|
+
# Hide the unapproved comments from people who are not book admins
|
398
|
+
unless current_user && current_user.book_admin
|
399
|
+
records = records.where(approved: true)
|
400
|
+
end
|
401
|
+
records
|
402
|
+
end
|
369
403
|
end
|
370
404
|
|
371
405
|
class BreedData
|
@@ -488,6 +522,9 @@ end
|
|
488
522
|
class CommentsController < JSONAPI::ResourceController
|
489
523
|
end
|
490
524
|
|
525
|
+
class FirmsController < JSONAPI::ResourceController
|
526
|
+
end
|
527
|
+
|
491
528
|
class SectionsController < JSONAPI::ResourceController
|
492
529
|
end
|
493
530
|
|
@@ -711,6 +748,13 @@ class CommentResource < JSONAPI::Resource
|
|
711
748
|
filters :body
|
712
749
|
end
|
713
750
|
|
751
|
+
class CompanyResource < JSONAPI::Resource
|
752
|
+
attributes :name, :address
|
753
|
+
end
|
754
|
+
|
755
|
+
class FirmResource < CompanyResource
|
756
|
+
end
|
757
|
+
|
714
758
|
class TagResource < JSONAPI::Resource
|
715
759
|
attributes :name
|
716
760
|
|
@@ -719,6 +763,12 @@ class TagResource < JSONAPI::Resource
|
|
719
763
|
#has_many :planets
|
720
764
|
end
|
721
765
|
|
766
|
+
class SpecialTagResource < JSONAPI::Resource
|
767
|
+
attributes :name
|
768
|
+
|
769
|
+
has_many :posts
|
770
|
+
end
|
771
|
+
|
722
772
|
class SectionResource < JSONAPI::Resource
|
723
773
|
attributes 'name'
|
724
774
|
end
|
@@ -733,6 +783,7 @@ class PostResource < JSONAPI::Resource
|
|
733
783
|
has_many :tags, acts_as_set: true
|
734
784
|
has_many :comments, acts_as_set: false
|
735
785
|
|
786
|
+
|
736
787
|
# Not needed - just for testing
|
737
788
|
primary_key :id
|
738
789
|
|
@@ -874,6 +925,10 @@ class PlanetResource < JSONAPI::Resource
|
|
874
925
|
has_one :planet_type
|
875
926
|
|
876
927
|
has_many :tags, acts_as_set: true
|
928
|
+
|
929
|
+
def records_for_moons
|
930
|
+
Moon.joins(:craters).select('moons.*, craters.code').distinct
|
931
|
+
end
|
877
932
|
end
|
878
933
|
|
879
934
|
class PropertyResource < JSONAPI::Resource
|
@@ -913,7 +968,7 @@ class PreferencesResource < JSONAPI::Resource
|
|
913
968
|
has_many :friends
|
914
969
|
|
915
970
|
def self.find_by_key(key, options = {})
|
916
|
-
new(Preferences.first)
|
971
|
+
new(Preferences.first, nil)
|
917
972
|
end
|
918
973
|
end
|
919
974
|
|
@@ -1104,15 +1159,8 @@ module Api
|
|
1104
1159
|
end
|
1105
1160
|
|
1106
1161
|
def records(options = {})
|
1107
|
-
|
1108
|
-
current_user
|
1109
|
-
|
1110
|
-
records = _model_class
|
1111
|
-
# Hide the unapproved comments from people who are not book admins
|
1112
|
-
unless current_user && current_user.book_admin
|
1113
|
-
records = records.where(approved_comments)
|
1114
|
-
end
|
1115
|
-
records
|
1162
|
+
current_user = options[:context][:current_user]
|
1163
|
+
_model_class.for_user(current_user)
|
1116
1164
|
end
|
1117
1165
|
end
|
1118
1166
|
end
|
@@ -1132,6 +1180,7 @@ module Api
|
|
1132
1180
|
ExpenseEntryResource = ExpenseEntryResource.dup
|
1133
1181
|
IsoCurrencyResource = IsoCurrencyResource.dup
|
1134
1182
|
|
1183
|
+
|
1135
1184
|
class BookResource < Api::V2::BookResource
|
1136
1185
|
paginator :paged
|
1137
1186
|
end
|
@@ -38,8 +38,8 @@ class RoutesTest < ActionDispatch::IntegrationTest
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def test_routing_posts_links_tags_destroy
|
41
|
-
assert_routing({path: '/posts/1/relationships/tags
|
42
|
-
{controller: 'posts', action: 'destroy_relationship', post_id: '1',
|
41
|
+
assert_routing({path: '/posts/1/relationships/tags', method: :delete},
|
42
|
+
{controller: 'posts', action: 'destroy_relationship', post_id: '1', relationship: 'tags'})
|
43
43
|
end
|
44
44
|
|
45
45
|
def test_routing_posts_links_tags_create
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class StiFieldsTest < ActionDispatch::IntegrationTest
|
4
|
+
def test_index_fields_when_resource_does_not_match_relationship
|
5
|
+
get "/posts", { filter: { id: "1,2" },
|
6
|
+
include: "author",
|
7
|
+
fields: { posts: "author", people: "email" } }
|
8
|
+
assert_response :success
|
9
|
+
assert_equal 2, json_response["data"].size
|
10
|
+
assert json_response["data"][0]["relationships"].key?("author")
|
11
|
+
assert json_response["included"][0]["attributes"].keys == ["email"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_fields_for_parent_class
|
15
|
+
get "/firms", { fields: { companies: "name" } }
|
16
|
+
assert_equal json_response["data"][0]["attributes"].keys, ["name"]
|
17
|
+
end
|
18
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -3,7 +3,9 @@ require 'simplecov'
|
|
3
3
|
# To run tests with coverage:
|
4
4
|
# COVERAGE=true rake test
|
5
5
|
# To Switch rails versions and run a particular test order:
|
6
|
-
# export RAILS_VERSION=4.2; bundle update rails; bundle exec rake TESTOPTS="--seed=39333" test
|
6
|
+
# export RAILS_VERSION=4.2.0; bundle update rails; bundle exec rake TESTOPTS="--seed=39333" test
|
7
|
+
# We are no longer having Travis test Rails 4.0.x. To test on Rails 4.0.x use this:
|
8
|
+
# export RAILS_VERSION=4.0.0; bundle update rails; bundle exec rake test
|
7
9
|
|
8
10
|
if ENV['COVERAGE']
|
9
11
|
SimpleCov.start do
|
@@ -115,9 +117,11 @@ JSONAPI.configuration.route_format = :underscored_route
|
|
115
117
|
TestApp.routes.draw do
|
116
118
|
jsonapi_resources :people
|
117
119
|
jsonapi_resources :comments
|
120
|
+
jsonapi_resources :firms
|
118
121
|
jsonapi_resources :tags
|
119
122
|
jsonapi_resources :posts do
|
120
123
|
jsonapi_relationships
|
124
|
+
jsonapi_links :special_tags
|
121
125
|
end
|
122
126
|
jsonapi_resources :sections
|
123
127
|
jsonapi_resources :iso_currencies
|
@@ -134,6 +138,8 @@ TestApp.routes.draw do
|
|
134
138
|
jsonapi_resources :documents
|
135
139
|
jsonapi_resources :products
|
136
140
|
|
141
|
+
|
142
|
+
|
137
143
|
namespace :api do
|
138
144
|
namespace :v1 do
|
139
145
|
jsonapi_resources :people
|