Cet article sera part3. Si vous n'avez pas vu part1 et part2, veuillez les consulter. (Très long)
↓part1 https://qiita.com/yoshi_4/items/6c9f3ced0eb20131903d ↓part2 https://qiita.com/yoshi_4/items/963bd1f5397caf8d7d67
Dans cette partie3, nous utiliserons l'authentification utilisateur implémentée dans la partie2 pour implémenter des actions qui ne peuvent être utilisées que lorsqu'une authentification telle qu'une action de création est effectuée. L'objectif cette fois est de mettre en œuvre des actions de création, de mise à jour et de destruction. Allons-y pour la première fois.
Tout d'abord, nous allons ajouter des points de terminaison. Et avant cela, faites un test une fois.
spec/routing/articles_spec.rb
it 'should route articles create' do
expect(post '/articles').to route_to('articles#create')
end
Puisque la requête http est post, l'action create est écrite avec post au lieu de get.
$ bundle exec rspec spec/routing/articles_spec.rb
No route matches "/articles"
Cela ressemblera à ceci, donc j'ajouterai le routage
config/routes.rb
resources :articles, only: [:index, :show, :create]
$ bundle exec rspec spec/routing/articles_spec.rb
Exécutez le test pour vous assurer qu'il réussit.
Et ensuite, j'écrirai un test pour le contrôleur.
spec/controllers/articles_controller_spec.rb
describe '#create' do
subject { post :create }
end
end
Ajoutez cette description à la fin.
Et j'écrirai également un test lorsque l'authentification ne fonctionne pas en utilisant les interdits_requests définis dans la partie 2
spec/controllers/articles_controller_spec.rb
describe '#create' do
subject { post :create }
context 'when no code provided' do
it_behaves_like 'forbidden_requests'
end
context 'when invalid code provided' do
before { request.headers['authorization'] = 'Invalid token' }
it_behaves_like 'forbidden_requests'
end
context 'when invalid parameters provided' do
end
end
Ceci interdit_rquests exécute un test qui s'attend à ce qu'un 403 soit renvoyé.
$ rspec spec/controllers/articles_controller_spec.rb
Ensuite, le message suivant sera retourné
The action 'create' could not be found for ArticlesController
On dit que l'action de création est introuvable, définissons-la.
app/controllers/articles_controller.rb
def create
end
Maintenant, relancez le test pour vous assurer que tout se passe bien. Si le test réussit, cela signifie que la certification fonctionne correctement.
Écrivons maintenant un test pour implémenter l'action de création.
spec/controllers/articles_controller_spec.rb
context 'when authorized' do
let(:access_token) { create :access_token }
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
context 'when invalid parameters provided' do
let(:invalid_attributes) do
{
data: {
attributes: {
title: '',
content: '',
}
}
}
end
subject { post :create, params: invalid_attributes }
it 'should return 422 status code' do
subject
expect(response).to have_http_status(:unprocessable_entity)
end
it 'should return proper error json' do
subject
expect(json['errors']).to include(
{
"source" => { "pointer" => "/data/attributes/title" },
"detail" => "can't be blank"
},
{
"source" => {"pointer" => "/data/attributes/content"},
"detail" => "can't be blank"
},
{
"source" => {"pointer" => "/data/attributes/slug"},
"detail" => "can't be blank"
}
)
end
end
context 'when success request sent' do
end
end
Ajout d'un test. J'ai beaucoup ajouté à la fois, mais chacun a de nombreuses pièces déjà arrivées et couvertes.
Le test ajouté est «lorsqu'il est autorisé», donc si l'authentification réussit, il sera testé. Chaque article à tester when invalid parameters provided should return 422 status code should return proper error json
Est ajouté. Si le paramètre est correct, je l'écrirai plus tard.
Si le paramètre est de, je m'attends à ne pas pouvoir être retourné. Le pointeur source montre où l'erreur se produit. Cette fois, tout est une chaîne de, donc je suppose que ce qui ne peut pas être vide sera renvoyé de tout.
Exécutez le test. Deux tests échouent.
expected the response to have status code :unprocessable_entity (422) but it was :no_content (204)
Tout d'abord, j'espère une réponse impossible à traiter, mais no_content est de retour. Je veux retourner no_content lorsque createa s'exécute avec succès, donc je vais le réparer plus tard.
unexpected token at ''
La seconde est l'erreur car JSON.parse donne une erreur avec la chaîne de caractères de.
Maintenant, implémentons-le sur le contrôleur et éliminons l'erreur.
app/controllers/articles_controller.rb
def create
article = Article.new(article_params)
if article.valid?
#we will figure that out
else
render json: article, adapter: :json_api,
serializer: ActiveModel::Serializer::ErrorSerializer,
status: :unprocessable_entity
end
end
private
def article_params
ActionController::Parameters.new
end
Je crée une instance d'ActionController :: Parameters car cela me permet d'utiliser StrongParameter. Vous pourrez utiliser permit et require, qui sont des méthodes d'instance d'ActionController :: Parameters. Si vous utilisez permit ou require, vous pouvez tronquer la partie inutile si elle est différente de ce que vous attendez formellement ou si un paramètre est envoyé avec une clé différente.
J'ai spécifié un adaptateur pour le rendu, qui spécifie le format. Si vous ne spécifiez pas cet adaptateur, les attributs sont spécifiés par défaut. Cette fois, j'utilise une personne appelée json_api. Ce qui suit montre la différence à titre d'exemple. Je l'ai copié à partir de Learn about Rails active_model_serializer_100DaysOfCode Challenge Day 10 (Day_10: # 100DaysOfCode).
attributes
[
{
"id": 1,
"name": "Nakajima Hikari",
"email": "[email protected]",
"birthdate": "2016-05-02",
"birthday": "2016/05/02"
}
]
}
json_api
{
"data": [
{
"id": "1",
"type": "contacts",
"attributes": {
"name": "Nakajima Hikari",
"email": "[email protected]",
"birthdate": "2016-05-02",
"birthday": "2016/05/02"
}
}
]
}
Cette fois, nous utiliserons json_api, qui convient aux api.
Exécutez le test et assurez-vous qu'il réussit.
Ensuite, j'écrirai un test lorsque le paramètre est correct.
spec/controllers/articles_controller_spec.rb
context 'when success request sent' do
let(:access_token) { create :access_token }
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
let(:valid_attributes) do
{
'data' => {
'attributes' => {
'title' => 'Awesome article',
'content' => 'Super content',
'slug' => 'awesome-article',
}
}
}
end
subject { post :create, params: valid_attributes }
it 'should have 201 status code' do
subject
expect(response).to have_http_status(:created)
end
it 'should have proper json body' do
subject
expect(json_data['attributes']).to include(
valid_attributes['data']['attributes']
)
end
it 'should create article' do
expect { subject }.to change{ Article.count }.by(1)
end
end
Vous avez entré le bon jeton et les bons paramètres. Maintenant, lancez le test.
expected the response to have status code :created (201) but it was :unprocessable_entity (422)
undefined method `[]' for nil:NilClass
`Article.count` to have changed by 1, but was changed by 0
Je pense que chacun des trois tests échouera de cette façon. Comme ils commettent les bonnes erreurs, nous implémenterons le contrôleur lorsque les paramètres seront réellement corrects.
app/controllers/articles_controller.rb
def create
article = Article.new(article_params)
article.save!
render json: article, status: :created
rescue
render json: article, adapter: :json_api,
serializer: ActiveModel::Serializer::ErrorSerializer,
status: :unprocessable_entity
end
private
def article_params
params.requrie(:data).require(:attributes).
permit(:title, :content, :slug) ||
ActionController::Parameters.new
end
Ensuite, modifiez créer comme ceci. Lorsqu'une erreur se produit, le sauvetage est utilisé pour ignorer l'erreur avec le rendu.
Dans article_params, en définissant une condition selon laquelle seul : title,: content ,: slug
in: attributes
dans: data
est acquis, tous les autres que ce format spécifié seront lus. Je suis.
Maintenant, quand je lance le test, tout passe.
Je vais faire un refactoring de plus.
app/controllers/articles_controller.rb
rescue
render json: article, adapter: :json_api,
serializer: ActiveModel::Serializer::ErrorSerializer,
status: :unprocessable_entity
end
Puisque ce ʻActiveModel :: Serializer :: ErrorSerializer, `est long, je vais l'hériter dans une classe différente ailleurs afin qu'il puisse être écrit court.
ʻCreate app / serializers / error_serializer.rb`
app/serializers/error_serializer.rb
class ErrorSerializer < ActiveModel::Serializer::ErrorSerializer; end
Qu'il soit hérité comme ça.
app/controllers/articles_controller.rb
rescue
render json: article, adapter: :json_api,
serializer: ErrorSerializer,
status: :unprocessable_entity
end
Et vous pouvez nettoyer la longue description. Exécutez un test pour voir s'il échoue.
Ceci termine la mise en œuvre de l'action pour créer un article.
Commençons par ajouter à nouveau des points de terminaison. Tout d'abord, j'écrirai un test.
spec/routing/articles_spec.rb
it 'should route articles show' do
expect(patch '/articles/1').to route_to('articles#update', id: '1')
end
J'écrirai des tests de point de terminaison comme à chaque fois. Puisque la requête http est patch ou put, l'action show utilise l'un ou l'autre.
Exécutez le test pour vous assurer d'obtenir l'erreur correctement.
config/routes.rb
resources :articles, only: [:index, :show, :create, :update]
Ajoutez une mise à jour pour vous assurer que le test réussit.
Ensuite, écrivons un test pour l'action de mise à jour du contrôleur.
spec/controllers/articles_controller_spec.rb
describe '#update' do
let(:article) { create :article }
subject { patch :update, params: { id: article.id } }
context 'when no code provided' do
it_behaves_like 'forbidden_requests'
end
context 'when invalid code provided' do
before { request.headers['authorization'] = 'Invalid token' }
it_behaves_like 'forbidden_requests'
end
context 'when authorized' do
let(:access_token) { create :access_token }
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
context 'when invalid parameters provided' do
let(:invalid_attributes) do
{
data: {
attributes: {
title: '',
content: '',
}
}
}
end
it 'should return 422 status code' do
subject
expect(response).to have_http_status(:unprocessable_entity)
end
it 'should return proper error json' do
subject
expect(json['errors']).to include(
{
"source" => { "pointer" => "/data/attributes/title" },
"detail" => "can't be blank"
},
{
"source" => {"pointer" => "/data/attributes/content"},
"detail" => "can't be blank"
},
{
"source" => {"pointer" => "/data/attributes/slug"},
"detail" => "can't be blank"
}
)
end
end
context 'when success request sent' do
let(:access_token) { create :access_token }
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
let(:valid_attributes) do
{
'data' => {
'attributes' => {
'title' => 'Awesome article',
'content' => 'Super content',
'slug' => 'awesome-article',
}
}
}
end
subject { post :create, params: valid_attributes }
it 'should have 201 status code' do
subject
expect(response).to have_http_status(:created)
end
it 'should have proper json body' do
subject
expect(json_data['attributes']).to include(
valid_attributes['data']['attributes']
)
end
it 'should create article' do
expect { subject }.to change{ Article.count }.by(1)
end
end
end
end
La différence entre l'action de mise à jour et l'action de création réside dans le type de demande et la mise à jour déjà dans la base de données. Puisqu'il n'y a qu'une situation où il y a un article à cibler, je viens de copier le test de création sauf pour la partie où l'article est créé en premier et la partie où la demande est définie.
Maintenant, lancez le test.
The action 'update' could not be found for ArticlesController
Je pense que vous obtiendrez une erreur comme celle-ci. Alors, définissons réellement la mise à jour.
app/controllers/articles_controller.rb
def update
article = Article.find(params[:id])
article.update_attributes!(article_params)
render json: article, status: :ok
rescue
render json: article, adapter: :json_api,
serializer: ErrorSerializer,
status: :unprocessable_entity
end
Ce n'est pas nouveau, donc je ne vais pas l'expliquer.
Maintenant, exécutez le test pour vous assurer que tout se passe bien. Si vous connaissez la différence entre créer et mettre à jour, vous pouvez voir qu'il n'y a presque aucune différence. Et vous pouvez réutiliser presque le même test.
Cependant, il y a un léger problème ici. Il peut être mis à jour sur l'article de n'importe qui sur demande. Je ne veux pas qu'il soit mis à jour sans autorisation. Alors je vais le réparer.
Quant à la façon de le résoudre, c'est un problème qui se produit parce que l'utilisateur et l'article ne sont pas liés pour le moment, nous allons donc ajouter une association à l'utilisateur et à l'article.
Avant cela, définissez l'association et testez que la valeur attendue est renvoyée.
spec/controllers/articles_controller_spec.rb
describe '#update' do
+ let(:user) { create :user }
let(:article) { create :article }
+ let(:access_token) { user.create_access_token }
subject { patch :update, params: { id: article.id } }
@ -140,8 +142,17 @@ describe ArticlesController do
it_behaves_like 'forbidden_requests'
end
+ context 'when trying to update not owned article' do
+ let(:other_user) { create :user }
+ let(:other_article) { create :article, user: other_user }
+
+ subject { patch :update, params: { id: other_article.id } }
+ before { request.headers['authorization'] = "Bearer #{access_token.token}" }
+
+ it_behaves_like 'forbidden_requests'
+ end
context 'when authorized' do
- let(:access_token) { create :access_token }
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
context 'when invalid parameters provided' do
let(:invalid_attributes) do
J'ai ajouté le test comme ça. Nous créons des articles qui se connectent avec les utilisateurs et même les authentifient.
Ce que je fais avec l'élément de test nouvellement ajouté est de vérifier si les interdictions_requests sont correctement renvoyées lorsque je tente de mettre à jour l'article d'un autre utilisateur.
Maintenant, lorsque vous exécutez le test
undefined method user=
Cela échouera avec un message comme celui-ci. Comme c'est la preuve que l'association n'a pas été établie, nous définirons l'association ensuite.
app/models/article.rb
belongs_to :user
app/models/user.rb
has_many :articles, dependent: :destroy
Et, pour connecter les deux modèles, il est nécessaire de donner au modèle d'article un user_id, alors ajoutez-le.
$ rails g migration AddUserToArticles user:references
$ rails db:migrate
Maintenant, l'association elle-même a été mise en place. Donc, en utilisant cela, nous modifierons la description du contrôleur.
app/controllers/articles_controller.rb
def update
article = current_user.articles.find(params[:id])
article.update_attributes!(article_params)
render json: article, status: :ok
rescue ActiveRecord::RecordNotFound
authorization_error
rescue
render json: article, adapter: :json_api,
serializer: ErrorSerializer,
status: :unprocessable_entity
end
Ce qui a changé dans la description, c'est que l'utilisateur à rechercher est appelé par current_user. Cela vous permet de rechercher uniquement à partir de l'utilisateur connecté. Et si l'id spécifié n'est pas dans l'article de current_user, ʻActiveRecord :: RecordNotFound` sera déclenché, alors sauvez-le comme ça et émettez une autorisation_error dédiée à l'authentification.
En outre, dans create, décrivez qui est l'article à créer et définissez user_id sur article. Je veux l'avoir, alors je vais faire quelques changements.
app/controllers/articles_controller.rb
def create
- article = Article.new(article_params)
+ article = current_user.articles.build(article_params)
Ensuite, ajoutez la description de l'association au bot d'usine.
spec/factories/articles.rb
FactoryBot.define do
factory :article do
sequence(:title) { |n| "My article #{n}"}
sequence(:content) { |n| "The content of article #{n}"}
sequence(:slug) { |n| "article-#{n}"}
association :user
end
end
association :model_name
Définira automatiquement l'id du modèle.
Si vous exécutez le test avec ceci, il réussira. Ensuite, passons à l'action de destruction.
Tout d'abord, écrivons un test pour ajouter un point de terminaison.
spec/routing/articles_spec.rb
it 'should route articles destroy' do
expect(delete '/articles/1').to route_to('articles#destroy', id: '1')
end
Lorsque j'exécute le test, j'obtiens le message suivant
No route matches "/articles/1"
Alors, éditons le routage.
config/routes.rb
resources :articles
Réglez tout sans spécifier avec la seule option. Cela passera le test de routage.
Ensuite, ajoutez un test pour le contrôleur.
spec/controllers/articles_controller_spec.rb
describe '#delete' do
let(:user) { create :user }
let(:article) { create :article, user_id: user.id }
let(:access_token) { user.create_access_token }
subject { delete :destroy, params: { id: article.id } }
context 'when no code provided' do
it_behaves_like 'forbidden_requests'
end
context 'when invalid code provided' do
before { request.headers['authorization'] = 'Invalid token' }
it_behaves_like 'forbidden_requests'
end
context 'when trying to remove not owned article' do
let(:other_user) { create :user }
let(:other_article) { create :article, user: other_user }
subject { delete :destroy, params: { id: other_article.id } }
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
it_behaves_like 'forbidden_requests'
end
context 'when authorized' do
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
it 'should have 204 status code' do
subject
expect(response).to have_http_status(:no_content)
end
it 'should have empty json body' do
subject
expect(response.body).to be_blank
end
it 'should destroy the article' do
article
expect{ subject }.to change{ user.articles.count }.by(-1)
end
end
end
La plupart du code de ce test est une copie du test de mise à jour. Le contenu n'a rien de nouveau. Exécutez le test.
The action 'destroy' could not be found for ArticlesController
Cette erreur est correcte car nous n'avons pas encore défini l'action de destruction. Puis contrôleur Sera mis en œuvre.
app/controllers/articles_controller.rb
def destroy
article = current_user.articles.find(params[:id])
article.destroy
head :no_content
rescue
authorization_error
end
Il détruit simplement l'article spécifié dans current_user.
Maintenant, lancez le test.
Si vous réussissez, tout est fait. Merci d'être resté longtemps avec nous!
Recommended Posts