Currently i am catching the error not_found like this
def show
begin
#task = Task.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
render json: { error: e.to_s }, status: :not_found and return
end
and the rspec test would be like this expect(response).to be_not_found
but i dont want to do that (rescue ActiveRecord::RecordNotFound => e) in every single function (update, create, destroy and so on)
there is another way?
for example this way
rescue_from ActiveRecord::RecordNotFound, with: :not_found
def not_found
respond_to do |format|
format.json { head :not_found }
end
end
but i dont know how can i test with that
i would like to test the same way
expect(response).to be_not_found
I think that your original implementation with an error node is a better response but your modified change is a better way to handle so I would suggest combining the 2 concepts via
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def not_found(exception)
respond_to do |format|
format.json do
# I expanded the response a bit but handle as you see fit
render json: {
error: {
message: 'Record Not Found',
data: {
record_type: exception.model,
id: exception.id
}
}
}, status: :not_found
end
end
end
You should be able to maintain your current test in this case while avoiding the need to individually handle in each request.
You can add the below code in your application_controller.rb.
around_filter :catch_not_found #=> only:[:show, :edit]
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound => e
respond_to do |format|
format.json { render json: { error: e.to_s }, status: :not_found and return }
format.html { redirect_to root_url, :flash => { :error => "Record not found." } and return }
end
end
Below is the simple example for test cases using RSpec. Modify as per your requirements.
staff_controller.rb
def show
#staff = Staff.find(params[:id])
end
RSpec
let(:staff) { FactoryBot.create(:staff) }
describe "GET #show" do
it "Renders show page for valid staff" do
get :show, {:id => staff.to_param}
expect(response).to render_template :show
end
it "redirects to root path on staff record not_found" do
get :show, id: 100
expect(response).to redirect_to(root_path)
end
end
Related
In a system spec, I'm trying to test the correct handling of a database timeout. When that happens a new TinyTds::Error is raised.
Here my controller (EMData handles the DB connection)
class Json::ChartController < ApplicationController
rescue_from TinyTds::Error, with: :connection_timed_out
def index
data = EMData.call(params)
respond_to do |format|
format.json { render json: data }
end
end
def connection_timed_out(_error)
format.json { head :request_timeout }
end
end
Here my spec
context 'when the SQL Server connection times out' do
let(:data_class) { class_spy('EMData').as_stubbed_const }
it 'a feedback message is displayed' do
allow(data_class).to receive(:call).and_raise(TinyTds::Error.new('message'))
...
SUBMIT FORM VIA JS
...
expect(page).to have_content("Some Content")
end
The spec seems pretty straightforward to me. However, when I run it , I get
Rack app error handling request { GET /json/chart/ }
/app/controllers/json/chart_controller.rb:24:in `format' ....
Failure/Error: format.json { head :request_timeout }
ArgumentError:
too few arguments
Am I misisng anything here?
You're missing the respond_to do |format| in connection_timed_out(_error). It should be like:
def connection_timed_out(_error)
respond_to do |format|
format.json { head :request_timeout }
end
end
I can't seem to get my head over how to make post requests for testing a url in request spec tests, here's the test code
RSpec.describe "Certifications", type: :request do
describe "denies public access" do
it "for new certification form" do
get new_certification_path
expect(response).to redirect_to new_user_session_path
end
it "for creating certification" do
certification_attributes = FactoryGirl.attributes_for :certification
expect {
post "/certifications", { certification: certification_attributes }
}.to_not change(Certification, :count)
expect(response).to redirect_to new_user_session_path
end
end
end
Which gives the error
1) Certifications denies public access for creating certification
Failure/Error: post "/certifications", { certification: certification_attributes }
ArgumentError:
unknown keyword: certification
I've tried the :certifications => certification_attributes, basically can't get my head over on how to pass params.
The controller under test is, adding only relevant methods to this post.
class CertificationsController < ApplicationController
skip_before_action :authenticate_user!, if: :skip_user_authentication
before_action :set_certification, only: [:show, :edit, :update, :destroy]
# GET /certifications
# GET /certifications.json
def index
#certifications = Certification.all
end
# GET /certifications/1
# GET /certifications/1.json
def show
end
# GET /certifications/new
def new
#certification = Certification.new
end
# POST /certifications
# POST /certifications.json
def create
#certification = Certification.new(certification_params)
respond_to do |format|
if #certification.save
format.html { redirect_to #certification, notice: 'Certification was successfully created.' }
format.json { render :show, status: :created, location: #certification }
else
format.html { render :new }
format.json { render json: #certification.errors, status: :unprocessable_entity }
end
end
end
protected
def skip_user_authentication
request.format.json? && (action_name.eql?('show') || (action_name.eql?('index')))
end
end
I am trying to assert the behaviour of allowing all methods except certifications.json or certifications/1.json to not require authentication, there are other tests which access these URLs and they pass. The part of ensuring it does not allow any other request is where I am stuck. I am using Devise with Omnitauth Google OAuth2 for authentication in this application.
certification_attributes
{
:name=>"Foundation Certification",
:description=>"Foundation Certification",
:terms=>"Foundation Certification",
:seo_meta_keywords=>["laaa", "lalala certifications"],
:seo_meta_description=>"Foundation Certifications"
}
Send request parameters under :params keyword:
post "/certifications", params: { certification: certification_attributes }
^^^^^^
Looks like you have some sort of authentication set up. You need to log the user in before attempting a POST.
Passing of the params to post looks OK. Tricky to say more without seeing your controller.
I have written an API in which I am rescuing from all StandardError. Incase of any StandardError, I send an exception email and render json with error message.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :respond_with_error
def respond_with_error(e)
ExceptionNotifier.notify_exception(e)
respond_to do |format|
format.html { render json: {error: e.message}, status: :unprocessable_entity, content_type: 'application/json' }
format.json { render json: {error: e.message}, status: :unprocessable_entity }
end
end
end
class UsersController < ApplicationController
def create
User.transaction do
#user = User.new(user_params)
authorize #user
#user.save!
end
respond_to do |format|
format.json { render :show, status: :created, location: #user }
end
end
end
This works great except when the exception is raised from render :show in UserController#create. As then when I again render json with error message from respond_with_error in ApplicationController, it raises double render exception cause render has already been called in controller.
Is there a way to override/dismiss the initial render call?
ruby 2.1.8
rails 4.2.6
Try adding the rescue block in the action itself.
def create
User.transaction do
#user = User.new(user_params)
authorize #user
#user.save!
end
flash.now[:success] = "successfully updated"
redirect_to #user and return
rescue StandardError => e
respond_with_error(e)
end
Just use redirect_to #user to render show page.
It's hard to say without looking at any code. But in general DoubleRenderException can be avoided with return, i.e
render :json => response_hash and return
This code is for a UserList (a user can create a User To-Do List). This particular resource does not hold the list items, but just the title of the list, and the type of list.
class Api::V1::UserListsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token
def index
if authenticate_user
user_lists = #current_user.user_lists
if user_lists
respond_with user_lists, each_serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's lists."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def show
if authenticate_user
user_lists = #current_user.user_lists
user_list = user_lists.find_by_id(params[:id])
if user_list
respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's list."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def create
if authenticate_user
user_list = #current_user.user_lists.new(user_list_params)
if (user_list.save!)
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not create new User List."}, status: :unprocessable_entity
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def update
if authenticate_user
user_list = #current_user.user_lists.find_by_id(params[:id])
if (user_list.update_attributes(user_list_update_params))
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
#respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not update User List." }, status: :unprocessable_entity
end
end
end
private
def user_list_params
params.require(:user_list).permit(:user_id, :type_id, :title)
end
def user_list_update_params
params.require(:user_list).permit(:type_id, :title)
end
end
Now the update works when I PUT/PATCH... but I get a
Completed 204 No Content in 24ms (ActiveRecord: 4.3ms)
It's been about 4+ months since I've done any rails, and back then I was only just beginning to learn it.
1) Does anyone know why I'm not getting anything back? I know it's something to do with my respond_with line of code in update, but I'm not sure exactly what.
2) Can someone clarify to me the difference between the SHOW respond_with and the CREATE respond_with. I recall having an issue grasping this back then, and obviously now.
SHOW
respond_with user_list, serializer: Api::V1::UserListSerializer
CREATE
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
a) Why does create require :api and :v1 first, but show does not?
b) Why does create require the #current_user, but show does not?
Appendix: Here is my Serializer for reference
class Api::V1::UserListSerializer < ActiveModel::Serializer
attributes :id, :user_id, :type_id, :title
has_many :items, embed: :ids
end
I know this is 2 years too late, but after some digging, I found the empty response with the 204 is intentional (as mentioned above). If you use respond_with this will always be the case. A workaround would be to use render instead (example below):
class Api::V1::ItemsController < ApplicationController
respond_to :json
...
def update
#item = Item.find(params[:id]
if #item
#item.update_attribute(item_params)
render json: #item
end
end
...
end
You're not supposed to get anything back other than the 204. Any intelligent client does not need to receive back the data it just sent you -- it needs only confirmation that the data was persisted.
Do not mistakenly pass your class Api::V1::UserListSerializer as a key/value pair (Hash form). You will get an error including the text class or module needed. It should look like this:
serialize :some_array, Api::V1::UserListSerializer
Or, perhaps clearer would be:
serialize(:some_array, Api::V1::UserListSerializer)
You miss one param and you are rendering an object class with no content : 204 - No Content
That may seem obvious, but it is common to be in the habit of passing things as a key/value pair.
One improve:
before_action :authenticate_user, only: [:create, :show, :update, ...]
https://apidock.com/rails/ActiveRecord/Base/serialize/class
def update
#item = Item.find(params[:id])
respond_with(:api, :v1, #item) do |format|
if #item.update(item_params)
format.json { render json: #item}
else
format.json { render json: {error: #item.errors.full_messages}}
end
end
end
I'm doing this treehouse project, some kind of social network built with ruby on rails (http://teamtreehouse.com/library/building-social-features-in-ruby-on-rails-2).
I was trying to make a user unable to update an existing status from another user and got stuck. This is my statuses_controller.rb:
class StatusesController < ApplicationController
before_filter :authenticate_user!, only: [:new, :create, :edit, :update]
before_action :set_status, only: [:show, :edit, :update, :destroy]
# GET /statuses
# GET /statuses.json
def index
#statuses = Status.order("created_at DESC").to_a
end
# GET /statuses/1
# GET /statuses/1.json
def show
end
# GET /statuses/new
def new
#status = Status.new
end
# GET /statuses/1/edit
def edit
end
# POST /statuses
# POST /statuses.json
def create
#status = current_user.statuses.new(status_params)
respond_to do |format|
if #status.save
format.html { redirect_to #status, notice: 'Status was successfully created.' }
format.json { render action: 'show', status: :created, location: #status }
else
format.html { render action: 'new' }
format.json { render json: #status.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /statuses/1
# PATCH/PUT /statuses/1.json
def update
#status = current_user.statuses.find(params[:id])
params[:status].delete(:user_id) if params[:status].has_key?(:user_id)
respond_to do |format|
if #status.update(status_params)
format.html { redirect_to #status, notice: 'Status was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #status.errors, status: :unprocessable_entity }
end
end
end
# DELETE /statuses/1
# DELETE /statuses/1.json
def destroy
#status.destroy
respond_to do |format|
format.html { redirect_to statuses_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_status
#status = Status.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def status_params
params.require(:status).permit(:content, :user_id)
end
end
While testing this (if a user 'should update status for the current user when logged in'), I got the following error:
1) Error:
StatusesControllerTest#test_should_update_status_for_the_current_user_when_logged_in:
ActiveRecord::RecordNotFound: Couldn't find Status with id=980190962 [WHERE "statuses"."user_id" = ?]
app/controllers/statuses_controller.rb:45:in `update'
test/controllers/statuses_controller_test.rb:76:in `block in <class:StatusesControllerTest>'
2) Error:
StatusesControllerTest#test_should_update_status_when_logged_in:
ActiveRecord::RecordNotFound: Couldn't find Status with id=980190962 [WHERE "statuses"."user_id" = ?]
app/controllers/statuses_controller.rb:45:in `update'
test/controllers/statuses_controller_test.rb:70:in `block in <class:StatusesControllerTest>'
12 tests, 24 assertions, 0 failures, 2 errors, 0 skips
This is my statuses_controller_test.rb:
test "should update status when logged in" do
sign_in users(:gustavo)
put :update, id: #status, status: { content: #status.content }
assert_redirected_to status_path(assigns(:status))
end
test "should update status for the current user when logged in" do
sign_in users(:gustavo)
put :update, id: #status, status: { content: #status.content, user_id: users(:paul).id }
assert_redirected_to status_path(assigns(:status))
assert_equal assigns(:status).user_id, users(:gustavo).id
end
And that's my fixture users.yml:
gustavo:
first_name: "Gustavo"
last_name: "Paiva"
email: "gustavorpaiva#gmail.com"
profile_name: "grpaiva"
paul:
first_name: "Paul"
last_name: "McCartney"
email: "paulmccartney#gmail.com"
profile_name: "paulpaul"
I'm using Rails 4.0.4, and I've already made some fixtures on treebook's code so it could work perfectly (i think it was made with Rails 2.x). Should that be the case or am I missing something here?
Ps.: This is my project link, before making this changes http://sheltered-everglades-2797.herokuapp.com/
Thanks!
Try #status.id in the put method instead of just #status
test "should update status when logged in" do
sign_in users(:gustavo)
put :update, id: #status.id, status: { content: #status.content }
assert_redirected_to status_path(assigns(:status))
end
If that doesn't work, can you post the code for how is the #status object is built in the controller test?