I'm having a problem when trying to use the return to in a rails controller. This is not working:
class UsersController < ApplicationController
respond_to :json
def create
#user = User.create params[:user_info]
respond_with #user
end
end
This works:
class UsersController < ApplicationController
respond_to :json
def create
#user = User.create params[:user_info]
respond_with #user do |format|
format.json { render json: #user.to_json }
end
end
end
Why? This is the error I have in the server's log when using the one that doesn't work:
NoMethodError (undefined method `user_url' for #<UsersController:0x007fd44d83ea90>):
app/controllers/users_controller.rb:7:in `create'
My route is:
resources :users, :only => [:create]
responds_with tries to redirect to user_url, so it looks for a show method in your user controller, which you don't have, since your route is limited to the create method only. Since the create method redirects to the show method by default this doesn't work. But in your second version you are actually rendering something, so no redirection happens.
You can give a :location option to respond_with if that's what you want, like so:
respond_with(#user, :location => home_url)
or use the render version as you do in your second version.
Related
I am implementing rspec test for destroy action, the concept is that signed in user can only destroy his own posts, and cannot destroy posts creates by other users.
The `new_post` is created by a user named `creator`, and another user named `user1` signed in and try to delete the `new_post`, it should not be able to delete it, because of the ` before_action :authenticate_user!, only: %i[create destroy]` in Posts controller
Posts controller.
class PostsController < ApplicationController
before_action :set_post, only: %i[show edit update destroy]
before_action :current_user, only: %i[create destroy]
before_action :authenticate_user!, only: %i[create destroy]
.
.
.
def destroy
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_post
#post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:content, :picture)
end
end
users controller spec
require 'rails_helper'
RSpec.describe PostsController, type: :controller do
context 'DELETE #destroy' do
let(:user1) {User.create!(name:"John", email:"john#mail.com", password:"password")}
let(:creator) { User.create!(name: "creator", email: "creaor#gmail.com", password: "password") }
let(:new_post){creator.posts.create!(content: "Neque porro quisquam est qui dolorem ipsum")}
it 'A user cannot delete a post created by other user' do
sign_in user1
p (new_post)
expect { delete :destroy, params: { id: new_post.id } }.to change(Post, :count).by(0)
end
end
end
Failures:
1) PostsController DELETE #destroy A user cannot delete a post created by other user
Failure/Error: expect { delete :destroy, params: { id: new_post.id } }.to change(Post, :count).by(0)
expected `Post.count` to have changed by 0, but was changed by -1
I believe you need to add an authorization check to your code. authenticate_user! authenticates that the person making the request is logged in. However, it does not check if the user is authorized to make the request they're making.
See Authentication versus Authorization for a bit more discussion on the two concepts. And take a look at https://stackoverflow.com/a/25654844/868533 for a good overview of popular authorization gems in Rails. To be clear, you almost definitely want a way to authenticate users (Devise) along with an authorization gem.
Assuming you decide to go with CanCanCan (which is a common option that I've used in past), you'd add an Ability class like:
class Ability
include CanCan::Ability
def initialize(user)
if user.present?
can :destroy, Post, user_id: user.id
end
end
end
Then you could add before_action :check_authorization, only: %i[destroy] as a new before_action on your controller and your tests should pass without any modification.
Remember. you are writing a controller test. So this test is unit test. There are two main ways to authorize in Devise. They are authorize routes and authorize controller. If you using authorize routes, when you write rspec for controller, you must use stub to counterfeit an authorize access.
I have this AdminController
class Admin::AdminController < ApplicationController
before_filter :is_admin?
def dashboard
end
def is_admin?
redirect_to root_path, :flash => { :alert => "You are not an admin!" } if !current_user.admin?
end
end
and this other controller that inherits from the above:
class Admin::CompetitionEntriesController < Admin::AdminController
before_action :set_competition_entry, only: [:show, :edit, :update, :destroy]
....
end
My route file is:
Foo::Application.routes.draw do
root 'competition_entries#index'
devise_for :users
resources :competition_entries
namespace :admin do
root 'admin#dashboard'
resources :competition_entries
end
....
..
.
end
Now why am I getting this error when I am trying to reach 'http://localhost:3000/admin'
Missing template admin/admin/dashboard...
I am getting this extra admin? Why? I don't want to use scopes I want to use namespaces.
Thanks.
Routes do not affect default search paths for templates. If your controller class is named Foo::BarController, Rails will look for the templates in app/views/foo/bar/ unless you specify otherwise.
Getting really bizarre rspec behavior in one of my controller specs.
It's best to illustrate. In rubymine, when I set a breakpoint, this happens:
#rspec test
describe Api::V1::UsersController do
let(:user) { FactoryGirl.create(:user) }
describe "#show" do
it "responds successfully" do
get 'show', id: user.id
response.should be_success
end
end
#controller
class Api::V1::UsersController < AuthenticatedController
def show # !!! RubyMine breakpoint will stop execution here !!!
user = User.find(params[:id])
user_hash = User.information(user, current_user)
respond_to do |format|
format.json { render json: user_hash.to_json }
end
end
So the above works as expected.
But, now this test fails.
#rspec test
describe UsersController do
let(:user) { FactoryGirl.create(:user, is_admin: false) }
describe "#show" do
it "redirects non-admin" do
get 'index'
response.should redirect_to user_path(user)
end
end
#controller
class UsersController < AuthenticatedController
def index # !!! Breakpoint is never hit !!!
#users = User.all
respond_to do |format|
if current_user.is_admin
format.html
format.json { render json: #users }
else
redirect_to user_path(current_user) and return
end
end
end
By the way, this is the result:
Expected response to be a redirect to <http://test.host/users/625> but was a redirect to <https://test.host/users>
None of my breakpoints in controller methods in UsersController are hit. BUT all controller methods are hit if I set breakpoints in API::V1::UsersController.
Any guidance is greatly appreciated. I'm really at a loss of how to debug this.
Sorry, this question was more out of frustation than anything. But I finally figured out what was going on. Hint: tailing the test.log is a good idea.
I was forcing ssl on the controller. The request rspec sent is http. ActionController::ForceSSL redirects the request to https and to the same controller#action. However, at this point, the rspec test was finished and failed the test because it only sees the redirection back to the same controller#action.
So in a before(:each) or something similar, use this: request.env['HTTPS'] = 'on'. All tests work as expected now.
I think maybe you're stepping outside of the domain of rspec here with regards to redirect testing. May I suggest using capybara and rspec?
My sources:
Rspec - Rails - How to follow a redirect
http://robots.thoughtbot.com/post/33771089985/rspec-integration-tests-with-capybara
Unfortunately, this is my second post in as many days. So the application worked fine with mysql and rails 3.0.3 but I found out that I needed to use MSSQL so I had to downgrade rails to 3.0.1.
In a nutshell, I copied the show.html.erb as show2.html.erb and created a new method which is a copy of the show method. Then I created a route match.
my controller
class fathersController < ApplicationController
def show
#father= Father.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #father}
end
end
def show2
#father= Father.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #father}
end
end
end
routes.rb
resources :fathers do
match '/show2' => 'fathers#show2'
resources :kids
end
when I call
http://127.0.0.1:3000/father/1
I get the show view but when I call
http://127.0.0.1:3000/father/1/show2
I get the following error
Couldn't find father without an ID
The request Parameters come back as
{"father_id"=>"1"}
so I know that the problem is that the app is passing the id as father_id but how do I fix it? Any help would be appreciated.
There are two problems.
You're trying to use a non-resourceful route on a route that actually should be resourceful.
It looks like you're trying to send /show2 to a controller named hospitals, when your action is actually specified on the fathers controller.
This should do the trick:
resources :fathers do
get :show2, :on => :member
resources :kids
end
You can also write the above as:
resources :fathers do
member do
get :show2
end
resources :kids
end
I am not finding much info on how to do this even though there are lots of suggestions on how to pass params to a redirect using hashs like this redirect_to
:action => 'something', :controller => 'something'
in my app I have the following in the routes file
match 'profile' => 'User#show'
my show action loos like this
def show
#user = User.find(params[:user])
#title = #user.first_name
end
the redirect happens in the same user controller like this
def register
#title = "Registration"
#user = User.new(params[:user])
if #user.save
redirect_to '/profile'
end
end
The question is in the register action when I redirect_to how do I pass along the params so I can grab that user from the database or better yet ... I already have a user variable so how do I pass along the user object to the show action?
-matthew
If you're doing a redirect, Rails will actually send a 302 Moved response with a URL to the browser and the browser will send another request to that URL. So you cannot "pass the user object" as in Ruby, you can only pass some url encoded parameters.
In this case you would probably want to change your routing definition to:
match 'profile/:id' => 'User#show'
and then redirect like this:
redirect_to "/profile/#{#user.id}"
First off, I'd name your route, to make using it easier:
match '/profile/:id' => 'users#show', :as => :profile
You would then redirect to it, like so:
redirect_to profile_path(#user) # might have to use profile_path(:id => #user.id)
Then to pull the user from the database:
def show
#user = User.find(params[:id]) # :id comes from the route '/profile/:id'
...
end
As an aside, if you use something like Devise for authentication, it provides you with a current_user method, and therefore you wont need to pass around the user's id:
match '/profile' => 'users#show', :as => :profile
redirect_to profile_path
def show
#user = current_user
end