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