diff --git a/README.md b/README.md index 9b7bf28..27d6f4a 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,13 @@ Support: For Rails 4 add this to your application's Gemfile: ```ruby -gem 'jsonapi-utils', '~> 0.4.8' +gem 'jsonapi-utils', '~> 0.4.9' ``` For Rails 5: ```ruby -gem 'jsonapi-utils', '~> 0.5.1' +gem 'jsonapi-utils', '~> 0.5.2' ``` And then execute: diff --git a/jsonapi-utils.gemspec b/jsonapi-utils.gemspec index e5e57aa..142fd10 100644 --- a/jsonapi-utils.gemspec +++ b/jsonapi-utils.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_runtime_dependency 'jsonapi-resources', '~> 0.8.0' + spec.add_runtime_dependency 'jsonapi-resources', '0.8.3' spec.add_development_dependency 'bundler', '~> 1.10' spec.add_development_dependency 'rake', '~> 10.0' diff --git a/lib/jsonapi/utils/version.rb b/lib/jsonapi/utils/version.rb index 58657ff..aa7a9ec 100644 --- a/lib/jsonapi/utils/version.rb +++ b/lib/jsonapi/utils/version.rb @@ -1,5 +1,5 @@ module JSONAPI module Utils - VERSION = '0.5.1'.freeze + VERSION = '0.5.2'.freeze end end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 4449455..47ad7ce 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -3,17 +3,19 @@ describe PostsController, type: :controller do include_context 'JSON API headers' - before(:all) { FactoryGirl.create_list(:post, 3) } + before(:all) do + @post = FactoryGirl.create_list(:post, 3).first + end before(:each) do JSONAPI.configuration.json_key_format = :underscored_key end - let(:fields) { (PostResource.fields - %i(id author category)).map(&:to_s) } - let(:relationships) { %w(author category) } - let(:resource) { Post.first } - let(:parent_id) { resource.user_id } - let(:category_id) { resource.category_id } + let(:relationships) { PostResource._relationships.keys.map(&:to_s) } + let(:fields) { PostResource.fields.reject { |e| e == :id }.map(&:to_s) - relationships } + let(:blog_post) { @post } + let(:parent_id) { blog_post.user_id } + let(:category_id) { blog_post.category_id } let(:attributes) do { title: 'Lorem ipsum', body: 'Lorem ipsum dolor sit amet.', content_type: 'article' } @@ -38,7 +40,7 @@ end describe 'GET #index' do - subject { get :index, params } + subject { get :index, params: params } let(:params) { { user_id: parent_id } } @@ -53,7 +55,7 @@ end context 'with Hash' do - subject { get :index_with_hash, params } + subject { get :index_with_hash, params: params } it 'renders a collection of users' do expect(subject).to have_http_status :ok @@ -148,19 +150,19 @@ describe 'GET #show' do context 'with ActiveRecord' do - subject { get :show, id: resource.id } + subject { get :show, params: { id: blog_post.id } } it 'renders a single post' do expect(subject).to have_http_status :ok expect(subject).to have_primary_data('posts') expect(subject).to have_data_attributes(fields) expect(subject).to have_relationships(relationships) - expect(data.dig('attributes', 'title')).to eq("Title for Post #{resource.id}") + expect(data.dig('attributes', 'title')).to eq("Title for Post #{blog_post.id}") end end context 'with Hash' do - subject { get :show_with_hash, id: resource.id } + subject { get :show_with_hash, params: { id: blog_post.id } } it 'renders a single post' do expect(subject).to have_http_status :ok @@ -173,7 +175,7 @@ context 'when resource was not found' do context 'with conventional id' do - subject { get :show, id: 999 } + subject { get :show, params: { id: 999 } } it 'renders a 404 response' do expect(subject).to have_http_status :not_found @@ -184,7 +186,7 @@ end context 'with uuid' do - subject { get :show, id: uuid } + subject { get :show, params: { id: uuid } } let(:uuid) { SecureRandom.uuid } @@ -197,7 +199,7 @@ end context 'with slug' do - subject { get :show, id: slug } + subject { get :show, params: { id: slug } } let(:slug) { 'some-awesome-slug' } @@ -212,7 +214,7 @@ end describe 'POST #create' do - subject { post :create, params.merge(body) } + subject { post :create, params: params.merge(body) } let (:params) { { user_id: parent_id } } @@ -225,7 +227,7 @@ end context 'when validation fails on an attribute' do - subject { post :create, params.merge(invalid_body) } + subject { post :create, params: params.merge(invalid_body) } let(:invalid_body) do body.tap { |b| b[:data][:attributes][:title] = nil } @@ -234,15 +236,15 @@ it 'renders a 422 response' do expect { subject }.to change(Post, :count).by(0) expect(response).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('title') - expect(errors[0]['title']).to eq('Title can\'t be blank') - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']['pointer']).to eq('/data/attributes/title') + expect(errors.dig(0, 'id')).to eq('title') + expect(errors.dig(0, 'title')).to eq('Title can\'t be blank') + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source', 'pointer')).to eq('/data/attributes/title') end end context 'when validation fails on a relationship' do - subject { post :create, params.merge(invalid_body) } + subject { post :create, params: params.merge(invalid_body) } let(:invalid_body) do body.tap { |b| b[:data][:relationships][:author] = nil } @@ -252,15 +254,15 @@ expect { subject }.to change(Post, :count).by(0) expect(subject).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('author') - expect(errors[0]['title']).to eq('Author can\'t be blank') - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']['pointer']).to eq('/data/relationships/author') + expect(errors.dig(0, 'id')).to eq('author') + expect(errors.dig(0, 'title')).to eq('Author can\'t be blank') + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source', 'pointer')).to eq('/data/relationships/author') end end context 'when validation fails on a foreign key' do - subject { post :create, params.merge(invalid_body) } + subject { post :create, params: params.merge(invalid_body) } let(:invalid_body) do body.tap { |b| b[:data][:relationships][:category] = nil } @@ -270,15 +272,15 @@ expect { subject }.to change(Post, :count).by(0) expect(subject).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('category') - expect(errors[0]['title']).to eq('Category can\'t be blank') - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']['pointer']).to eq('/data/relationships/category') + expect(errors.dig(0, 'id')).to eq('category') + expect(errors.dig(0, 'title')).to eq('Category can\'t be blank') + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source', 'pointer')).to eq('/data/relationships/category') end end context 'when validation fails on a private attribute' do - subject { post :create, params.merge(invalid_body) } + subject { post :create, params: params.merge(invalid_body) } let(:invalid_body) do body.tap { |b| b[:data][:attributes][:title] = 'Fail Hidden' } @@ -288,15 +290,15 @@ expect { subject }.to change(Post, :count).by(0) expect(subject).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('hidden_field') - expect(errors[0]['title']).to eq('Hidden field error was tripped') - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']).to be_nil + expect(errors.dig(0, 'id')).to eq('hidden_field') + expect(errors.dig(0, 'title')).to eq('Hidden field error was tripped') + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source', 'pointer')).to be_nil end end context 'when validation fails with a formatted attribute key' do - subject { post :create, params.merge(invalid_body) } + subject { post :create, params: params.merge(invalid_body) } let(:invalid_body) do body.tap { |b| b[:data][:attributes][:title] = 'Fail Hidden' } @@ -315,17 +317,17 @@ expect { subject }.to change(Post, :count).by(0) expect(subject).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('content-type') - expect(errors[0]['title']).to eq('Content type can\'t be blank') - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']['pointer']).to eq('/data/attributes/content-type') + expect(errors.dig(0, 'id')).to eq('content-type') + expect(errors.dig(0, 'title')).to eq('Content type can\'t be blank') + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source', 'pointer')).to eq('/data/attributes/content-type') end end end describe 'PATCH #update' do shared_context 'update request' do |action:| - subject { patch action, params.merge(body) } + subject { patch action, params: params.merge(body) } let(:params) { { id: 1 } } let(:body) { { data: { id: 1, type: 'posts', attributes: { title: 'Foo' } } } } @@ -343,10 +345,10 @@ expect { subject }.to change(Post, :count).by(0) expect(response).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('base') - expect(errors[0]['title']).to eq('This is an error on the base') - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']['pointer']).to eq('/data') + expect(errors.dig(0, 'id')).to eq('base') + expect(errors.dig(0, 'title')).to eq('This is an error on the base') + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source', 'pointer')).to eq('/data') end end end diff --git a/spec/controllers/profile_controller_spec.rb b/spec/controllers/profile_controller_spec.rb index 6d10d9c..7974b7f 100644 --- a/spec/controllers/profile_controller_spec.rb +++ b/spec/controllers/profile_controller_spec.rb @@ -3,9 +3,9 @@ describe ProfileController, type: :controller do include_context 'JSON API headers' - let(:fields) { (ProfileResource.fields - %i(id)).map(&:to_s) } - let(:resource) { Profile } - let(:attributes) { { location: 'Springfield, USA' } } + let(:relationships) { ProfileResource._relationships.keys.map(&:to_s) } + let(:fields) { ProfileResource.fields.reject { |e| e == :id }.map(&:to_s) - relationships } + let(:attributes) { { nickname: 'Foobar', location: 'Springfield, USA' } } let(:body) do { @@ -30,10 +30,10 @@ it 'renders a 422 response' do patch :update, params: body expect(response).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('location') - expect(errors[0]['title']).to eq("Location can't be blank") - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']['pointer']).to eq('/data/attributes/location') + expect(errors.dig(0, 'id')).to eq('nickname') + expect(errors.dig(0, 'title')).to eq("Nickname can't be blank") + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source', 'pointer')).to eq('/data/attributes/nickname') end end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 08ab1d2..6f0a5bd 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -3,14 +3,18 @@ describe UsersController, type: :controller do include_context 'JSON API headers' - before(:all) { FactoryGirl.create_list(:user, 3, :with_posts) } + before(:all) do + @user = FactoryGirl.create_list(:user, 3, :with_posts).first + end + + let(:user) { @user } before(:each) do JSONAPI.configuration.json_key_format = :underscored_key end - let(:fields) { (UserResource.fields - %i(id posts)).map(&:to_s) } - let(:relationships) { %w(posts) } + let(:relationships) { UserResource._relationships.keys.map(&:to_s) } + let(:fields) { UserResource.fields.reject { |e| e == :id }.map(&:to_s) - relationships } let(:attributes) { { first_name: 'Yehuda', last_name: 'Katz' } } let(:user_params) do @@ -30,7 +34,7 @@ context 'with "include"' do it 'returns only the required relationships in the "included" member' do - get :index, include: :posts + get :index, params: { include: 'profile,posts' } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(response).to have_data_attributes(fields) @@ -41,7 +45,7 @@ context 'with "fields"' do it 'returns only the required fields in the "attributes" member' do - get :index, fields: { users: :first_name } + get :index, params: { fields: { users: :first_name } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(response).to have_data_attributes(%w(first_name)) @@ -49,24 +53,23 @@ end context 'with "filter"' do - let(:user) { User.first } let(:first_name) { user.first_name } let(:full_name) { "#{user.first_name} #{user.last_name}" } it 'returns only results corresponding to the applied filter' do - get :index, filter: { first_name: first_name } + get :index, params: { filter: { first_name: first_name } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(response).to have_meta_record_count(1) - expect(data[0]['attributes']['first_name']).to eq(first_name) + expect(data.dig(0, 'attributes', 'first_name')).to eq(first_name) end it 'returns only results corresponding to the applied custom filter' do - get :index, filter: { full_name: full_name } + get :index, params: { filter: { full_name: full_name } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(response).to have_meta_record_count(1) - expect(data[0]['attributes']['full_name']).to eq(full_name) + expect(data.dig(0, 'attributes', 'full_name')).to eq(full_name) end context 'when using "dasherized_key"' do @@ -75,10 +78,10 @@ end it 'returns only results corresponding to the applied filter' do - get :index, filter: { 'first-name' => first_name } + get :index, params: { filter: { 'first-name' => first_name } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') - expect(data[0]['attributes']['first-name']).to eq(first_name) + expect(data.dig(0, 'attributes', 'first-name')).to eq(first_name) end end @@ -88,10 +91,10 @@ end it 'returns only results corresponding to the applied filter' do - get :index, filter: { 'firstName' => first_name } + get :index, params: { filter: { 'firstName' => first_name } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') - expect(data[0]['attributes']['firstName']).to eq(first_name) + expect(data.dig(0, 'attributes', 'firstName')).to eq(first_name) end end end @@ -104,70 +107,69 @@ context 'at the first page' do it 'returns paginated results' do - get :index, page: { number: 1, size: 2 } + get :index, params: { page: { number: 1, size: 2 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(2) expect(response).to have_meta_record_count(3) - expect(json['links']['first']).to be_present - expect(json['links']['next']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'next')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'at the middle' do it 'returns paginated results' do - get :index, page: { number: 2, size: 1 } + get :index, params: { page: { number: 2, size: 1 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(1) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['prev']).to be_present - expect(json['links']['next']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'prev')).to be_present + expect(json.dig('links', 'next')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'at the last page' do it 'returns paginated results' do - get :index, page: { number: 3, size: 1 } + get :index, params: { page: { number: 3, size: 1 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(1) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['prev']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'prev')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'when filtering with pagination' do - let(:user) { FactoryGirl.create(:user) } let(:count) { User.where(user.slice(:first_name, :last_name)).count } it 'returns paginated results according to the given filter' do - get :index, filter: { full_name: user.full_name }, page: { number: 1, size: 2 } + get :index, params: { filter: { full_name: user.full_name }, page: { number: 1, size: 2 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(1) expect(response).to have_meta_record_count(count) - expect(data[0]['attributes']['full_name']).to eq(user.full_name) + expect(data.dig(0, 'attributes', 'full_name')).to eq(user.full_name) end end context 'without "size"' do it 'returns the amount of results based on "JSONAPI.configuration.default_page_size"' do - get :index, page: { number: 1 } + get :index, params: { page: { number: 1 } } expect(response).to have_http_status :ok expect(data.size).to be <= JSONAPI.configuration.default_page_size expect(response).to have_meta_record_count(User.count) @@ -182,53 +184,53 @@ context 'at the first page' do it 'returns paginated results' do - get :index, page: { offset: 0, limit: 2 } + get :index, params: { page: { offset: 0, limit: 2 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(2) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['next']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'next')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'at the middle' do it 'returns paginated results' do - get :index, page: { offset: 1, limit: 1 } + get :index, params: { page: { offset: 1, limit: 1 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(1) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['prev']).to be_present - expect(json['links']['next']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'prev')).to be_present + expect(json.dig('links', 'next')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'at the last page' do it 'returns the paginated results' do - get :index, page: { offset: 2, limit: 1 } + get :index, params: { page: { offset: 2, limit: 1 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(1) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['prev']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'prev')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'without "limit"' do it 'returns the amount of results based on "JSONAPI.configuration.default_page_size"' do - get :index, page: { offset: 1 } + get :index, params: { page: { offset: 1 } } expect(response).to have_http_status :ok expect(data.size).to be <= JSONAPI.configuration.default_page_size expect(response).to have_meta_record_count(User.count) @@ -243,53 +245,53 @@ context 'at the first page' do it 'returns paginated results' do - get :index, page: { offset: 0, limit: 2 } + get :index, params: { page: { offset: 0, limit: 2 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(2) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['next']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'next')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'at the middle' do it 'returns paginated results' do - get :index, page: { offset: 1, limit: 1 } + get :index, params: { page: { offset: 1, limit: 1 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(1) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['prev']).to be_present - expect(json['links']['next']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'prev')).to be_present + expect(json.dig('links', 'next')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'at the last page' do it 'returns the paginated results' do - get :index, page: { offset: 2, limit: 1 } + get :index, params: { page: { offset: 2, limit: 1 } } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(data.size).to eq(1) expect(response).to have_meta_record_count(User.count) - expect(json['links']['first']).to be_present - expect(json['links']['prev']).to be_present - expect(json['links']['last']).to be_present + expect(json.dig('links', 'first')).to be_present + expect(json.dig('links', 'prev')).to be_present + expect(json.dig('links', 'last')).to be_present end end context 'without "limit"' do it 'returns the amount of results based on "JSONAPI.configuration.default_page_size"' do - get :index, page: { offset: 1 } + get :index, params: { page: { offset: 1 } } expect(response).to have_http_status :ok expect(data.size).to be <= JSONAPI.configuration.default_page_size expect(response).to have_meta_record_count(User.count) @@ -301,10 +303,10 @@ context 'with "sort"' do context 'when asc' do it 'returns sorted results' do - get :index, sort: :first_name + get :index, params: { sort: :first_name } - first_name1 = data[0]['attributes']['first_name'] - first_name2 = data[1]['attributes']['first_name'] + first_name1 = data.dig(0, 'attributes', 'first_name') + first_name2 = data.dig(1, 'attributes', 'first_name') expect(response).to have_http_status :ok expect(response).to have_primary_data('users') @@ -314,10 +316,10 @@ context 'when desc' do it 'returns sorted results' do - get :index, sort: '-first_name,-last_name' + get :index, params: { sort: '-first_name,-last_name' } - first_name_1, last_name_1 = data[0]['attributes'].values_at('first_name', 'last_name') - first_name_2, last_name_2 = data[1]['attributes'].values_at('first_name', 'last_name') + first_name_1, last_name_1 = data.dig(0, 'attributes').values_at('first_name', 'last_name') + first_name_2, last_name_2 = data.dig(1, 'attributes').values_at('first_name', 'last_name') sorted = first_name_1 > first_name_2 || (first_name_1 == first_name_2 && last_name_1 >= last_name_2) expect(response).to have_http_status :ok @@ -332,10 +334,10 @@ end it 'returns sorted results' do - get :index, sort: 'first-name' + get :index, params: { sort: 'first-name' } - first_name_1 = data[0]['attributes']['first-name'] - first_name_2 = data[1]['attributes']['first-name'] + first_name_1 = data.dig(0, 'attributes', 'first-name') + first_name_2 = data.dig(1, 'attributes', 'first-name') expect(response).to have_http_status :ok expect(response).to have_primary_data('users') @@ -349,10 +351,10 @@ end it 'returns sorted results' do - get :index, sort: 'firstName' + get :index, params: { sort: 'firstName' } - first_name_1 = data[0]['attributes']['firstName'] - first_name_2 = data[1]['attributes']['firstName'] + first_name_1 = data.dig(0, 'attributes', 'firstName') + first_name_2 = data.dig(1, 'attributes', 'firstName') expect(response).to have_http_status :ok expect(response).to have_primary_data('users') @@ -363,19 +365,17 @@ end describe '#show' do - let(:user) { User.first } - it 'renders a single user' do - get :show, id: user.id + get :show, params: { id: user.id } expect(response).to have_http_status :ok expect(response).to have_primary_data('users') expect(response).to have_data_attributes(fields) - expect(data['attributes']['first_name']).to eq("User##{user.id}") + expect(data.dig('attributes', 'first_name')).to eq("User##{user.id}") end context 'when resource was not found' do it 'renders a 404 response' do - get :show, id: 999 + get :show, params: { id: 999 } expect(response).to have_http_status :not_found expect(error['title']).to eq('Record not found') expect(error['code']).to eq('404') @@ -385,17 +385,18 @@ describe '#create' do it 'creates a new user' do - expect { post :create, user_params }.to change(User, :count).by(1) + expect { post :create, params: user_params }.to change(User, :count).by(1) expect(response).to have_http_status :created expect(response).to have_primary_data('users') expect(response).to have_data_attributes(fields) - expect(data['attributes']['first_name']).to eq(user_params[:data][:attributes][:first_name]) + expect(data.dig('attributes', 'first_name')).to eq(user_params.dig(:data, :attributes, :first_name)) end shared_examples_for '400 response' do |hash| + before { user_params.dig(:data, :attributes).merge!(hash) } + it 'renders a 400 response' do - user_params[:data][:attributes].merge!(hash) - expect { post :create, user_params }.to change(User, :count).by(0) + expect { post :create, params: user_params }.to change(User, :count).by(0) expect(response).to have_http_status :bad_request expect(error['title']).to eq('Param not allowed') expect(error['code']).to eq('105') @@ -411,27 +412,26 @@ end context 'when validation fails' do - it 'renders a 422 response' do - user_params[:data][:attributes].merge!(first_name: nil, last_name: nil) + before { user_params.dig(:data, :attributes).merge!(first_name: nil, last_name: nil) } - expect { post :create, user_params }.to change(User, :count).by(0) + it 'renders a 422 response' do + expect { post :create, params: user_params }.to change(User, :count).by(0) expect(response).to have_http_status :unprocessable_entity - expect(errors[0]['id']).to eq('first_name') - expect(errors[0]['title']).to eq('First name can\'t be blank') - expect(errors[0]['code']).to eq('100') - expect(errors[0]['source']).to be_nil + expect(errors.dig(0, 'id')).to eq('first_name') + expect(errors.dig(0, 'title')).to eq('First name can\'t be blank') + expect(errors.dig(0, 'code')).to eq('100') + expect(errors.dig(0, 'source')).to be_nil - expect(errors[1]['id']).to eq('last_name') - expect(errors[1]['title']).to eq('Last name can\'t be blank') - expect(errors[1]['code']).to eq('100') - expect(errors[1]['source']).to be_nil + expect(errors.dig(1, 'id')).to eq('last_name') + expect(errors.dig(1, 'title')).to eq('Last name can\'t be blank') + expect(errors.dig(1, 'code')).to eq('100') + expect(errors.dig(1, 'source')).to be_nil end end end describe '#update' do - let(:user) { User.first } let(:post) { user.posts.first } let(:update_params) do @@ -448,7 +448,7 @@ end it 'update an existing user' do - patch :update, update_params + patch :update, params: update_params expect(response).to have_http_status :ok expect(response).to have_primary_data('users') @@ -460,9 +460,10 @@ end context 'when resource was not found' do + before { update_params[:data][:id] = 999 } + it 'renders a 404 response' do - update_params[:data][:id] = 999 - patch :update, update_params.merge(id: 999) + patch :update, params: update_params.merge(id: 999) expect(response).to have_http_status :not_found expect(error['title']).to eq('Record not found') expect(error['code']).to eq('404') @@ -470,9 +471,10 @@ end context 'when validation fails' do + before { update_params[:data][:attributes].merge!(first_name: nil, last_name: nil) } + it 'render a 422 response' do - update_params[:data][:attributes].merge!(first_name: nil, last_name: nil) - patch :update, update_params + patch :update, params: update_params expect(response).to have_http_status :unprocessable_entity expect(errors[0]['id']).to eq('my_custom_validation_error') expect(errors[0]['title']).to eq('My custom error message') @@ -481,4 +483,14 @@ end end end + + describe 'use of JSONAPI::Resources\' default actions' do + describe '#show_relationship' do + it 'renders the user\'s profile' do + get :show_relationship, params: { user_id: user.id, relationship: 'profile' } + expect(response).to have_http_status :ok + expect(response).to have_primary_data('profiles') + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bb97a96..c3738c1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -92,6 +92,7 @@ class TestApp < Rails::Application TestApp.routes.draw do jsonapi_resources :users do + jsonapi_links :profile jsonapi_resources :posts, shallow: true end diff --git a/spec/support/factories.rb b/spec/support/factories.rb index 182edaa..d99b29e 100644 --- a/spec/support/factories.rb +++ b/spec/support/factories.rb @@ -21,6 +21,8 @@ sequence(:first_name) { |n| "User##{n}" } sequence(:last_name) { |n| "Lastname##{n}" } + after(:create) { |user| create(:profile, user: user) } + trait :with_posts do transient { post_count 3 } after(:create) do |user, e| @@ -28,4 +30,11 @@ end end end + + factory :profile, class: Profile do + user + sequence(:id) { |n| n } + sequence(:nickname) { |n| "Nickname##{n}" } + sequence(:location) { |n| "Location##{n}" } + end end diff --git a/spec/support/models.rb b/spec/support/models.rb index f6b655b..90b3595 100644 --- a/spec/support/models.rb +++ b/spec/support/models.rb @@ -26,10 +26,18 @@ t.boolean :admin t.timestamps null: false end + + create_table :profiles, force: true do |t| + t.references :user, index: true, foreign_key: true + t.string :nickname + t.string :location + t.timestamps null: false + end end # Models class User < ActiveRecord::Base + has_one :profile has_many :posts validates :first_name, :last_name, presence: true @@ -56,8 +64,7 @@ class Category < ActiveRecord::Base validates :title, presence: true end -class Profile - include ActiveModel::Model - attr_accessor :id, :location - validates :location, presence: true +class Profile < ActiveRecord::Base + belongs_to :user + validates :nickname, :location, presence: true end diff --git a/spec/support/resources.rb b/spec/support/resources.rb index 0e6d752..3146451 100644 --- a/spec/support/resources.rb +++ b/spec/support/resources.rb @@ -16,6 +16,8 @@ class PostResource < ::PostResource; end class UserResource < JSONAPI::Resource attributes :first_name, :last_name, :full_name + has_one :profile, class_name: 'Profile' + has_many :posts filters :first_name @@ -28,5 +30,6 @@ def full_name end class ProfileResource < JSONAPI::Resource - attribute :location + attributes :nickname, :location + has_one :user, class_name: 'User', foreign_key: 'user_id' end diff --git a/spec/support/shared/jsonapi_errors.rb b/spec/support/shared/jsonapi_errors.rb index da3807f..4805c75 100644 --- a/spec/support/shared/jsonapi_errors.rb +++ b/spec/support/shared/jsonapi_errors.rb @@ -3,7 +3,7 @@ context 'with "include"' do context 'when resource does not exist' do it 'renders a 400 response' do - get :index, include: :foobar + get :index, params: { include: :foobar } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid field') expect(error['code']).to eq('112') @@ -14,7 +14,7 @@ context 'with "fields"' do context 'when resource does not exist' do it 'renders a 400 response' do - get :index, fields: { foo: 'bar' } + get :index, params: { fields: { foo: 'bar' } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid resource') expect(error['code']).to eq('101') @@ -23,7 +23,7 @@ context 'when field does not exist' do it 'renders a 400 response' do - get :index, fields: { users: 'bar' } + get :index, params: { fields: { users: 'bar' } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid field') expect(error['code']).to eq('104') @@ -34,7 +34,7 @@ context 'with "filter"' do context 'when filter is not allowed' do it 'renders a 400 response' do - get :index, filter: { foo: 'bar' } + get :index, params: { filter: { foo: 'bar' } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Filter not allowed') expect(error['code']).to eq('102') @@ -48,7 +48,7 @@ context 'with invalid number' do it 'renders a 400 response' do - get :index, page: { number: 'foo' } + get :index, params: { page: { number: 'foo' } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -57,7 +57,7 @@ context 'with invalid size' do it 'renders a 400 response' do - get :index, page: { size: 'foo' } + get :index, params: { page: { size: 'foo' } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -66,14 +66,14 @@ context 'with invalid page param' do it 'renders a 400 response' do - get :index, page: { offset: 1 } + get :index, params: { page: { offset: 1 } } expect(response).to have_http_status :ok end end context 'with a "size" greater than the max limit' do it 'returns the amount of results based on "JSONAPI.configuration.maximum_page_size"' do - get :index, page: { size: 999 } + get :index, params: { page: { size: 999 } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -86,7 +86,7 @@ context 'with invalid offset' do it 'renders a 400 response' do - get :index, page: { offset: -1 } + get :index, params: { page: { offset: -1 } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -95,7 +95,7 @@ context 'with invalid limit' do it 'renders a 400 response' do - get :index, page: { limit: 'foo' } + get :index, params: { page: { limit: 'foo' } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -104,14 +104,14 @@ context 'with invalid page param' do it 'renders a 400 response' do - get :index, page: { size: 1 } + get :index, params: { page: { size: 1 } } expect(response).to have_http_status :ok end end context 'with a "limit" greater than the max limit' do it 'returns the amount of results based on "JSONAPI.configuration.maximum_page_size"' do - get :index, page: { limit: 999 } + get :index, params: { page: { limit: 999 } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -124,7 +124,7 @@ context 'with invalid offset' do it 'renders a 400 response' do - get :index, page: { offset: -1 } + get :index, params: { page: { offset: -1 } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -133,7 +133,7 @@ context 'with invalid limit' do it 'renders a 400 response' do - get :index, page: { limit: 'foo' } + get :index, params: { page: { limit: 'foo' } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -142,14 +142,14 @@ context 'with invalid page param' do it 'renders a 400 response' do - get :index, page: { size: 1 } + get :index, params: { page: { size: 1 } } expect(response).to have_http_status :ok end end context 'with a "limit" greater than the max limit' do it 'returns the amount of results based on "JSONAPI.configuration.maximum_page_size"' do - get :index, page: { limit: 999 } + get :index, params: { page: { limit: 999 } } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid page value') expect(error['code']).to eq('118') @@ -161,7 +161,7 @@ context 'with "sort"' do context 'when sort criteria is invalid' do it 'renders a 400 response' do - get :index, sort: 'foo' + get :index, params: { sort: 'foo' } expect(response).to have_http_status :bad_request expect(error['title']).to eq('Invalid sort criteria') expect(error['code']).to eq('114')