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.
@@ -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