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.
@@ -22,7 +22,7 @@ module JSONAPI
22
22
  :remove_to_one_link,
23
23
  :replace_fields
24
24
 
25
- def initialize(model, context = nil)
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, _options = {})
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
- relationship = self.class._relationships[relationship_type]
210
+ relation_name = self.class._relationships[relationship_type].relation_name(context: @context)
212
211
 
213
- @model.public_send(relationship.type).delete(key)
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('::', '/') + '/').downcase : ''
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 |options = {}|
751
- options = options.merge({context: @context})
752
- relation_name = relationship.relation_name(options)
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(model)
126
- @fields[model] if @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._type)
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._type)
149
+ requested = requested_fields(source.class)
145
150
  fields = relationships.keys
146
151
  fields = requested & fields unless requested.nil?
147
152
 
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = '0.5.9'
3
+ VERSION = '0.6.0'
4
4
  end
5
5
  end
@@ -154,7 +154,7 @@ module ActionDispatch
154
154
  end
155
155
 
156
156
  if methods.include?(:destroy)
157
- match "relationships/#{formatted_relationship_name}/:keys", controller: options[:controller],
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
@@ -1,6 +1,5 @@
1
1
  test:
2
2
  adapter: sqlite3
3
- database: "test_db"
4
- # database: ":memory:"
3
+ database: test_db
5
4
  pool: 5
6
5
  timeout: 5000
@@ -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', keys: '3'}
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', keys: '4'}
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
- context = options[:context]
1108
- current_user = context ? context[:current_user] : nil
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
@@ -0,0 +1,4 @@
1
+ firm1:
2
+ type: Firm
3
+ name: JSON Consulting Services
4
+ address: 456 1st Ave.
@@ -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/1,2', method: :delete},
42
- {controller: 'posts', action: 'destroy_relationship', post_id: '1', keys: '1,2', relationship: 'tags'})
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
@@ -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