Why does my test try to render a different & invalid route? - ruby-on-rails

I have a test that tries to view a subscription that doesn't exist. I run this test with a bunch of users from my fixtures. In the case of the user in the admin role when the app gets to the point of trying to render the response it has changed the action from :show to :edit and dropped the id param. Yet when I try and use byebug to trace the execution I never seem to be able to pinpoint when it happens.
my test is:
test "#{u.role} can not view subscriptions that don't exist" do
self.send('sign_in_' + u.role)
get :show, id:1234567890
assert_redirected_to root_path
assert_includes flash[:alert], "That subscription doesn't exist"
end
where u is a user loaded from my fixtures.
The error I get is:
SubscriptionsControllerTest#test_admin_can_not_view_subscriptions_that_don't_exist:
ActionView::Template::Error: No route matches {:action=>"edit", :controller=>"subscriptions", :id=>nil} missing required keys: [:id]
app/views/subscriptions/show.html.erb:13:in `_app_views_subscriptions_show_html_erb__1518678276755260966_70268849069860'
test/controllers/subscriptions_controller_test.rb:58:in `block (2 levels) in <class:SubscriptionsControllerTest>'
my controller looks like this:
class SubscriptionsController < ApplicationController
load_and_authorize_resource except: [:create,:new]
before_action :set_subscription
def show
end
def edit
end
...
private
def subscription_params
params.require(:subscription).permit(:email,:confirmed)
end
def set_subscription
#byebug if user_signed_in? && current_user.role == 'admin' && self.action_name == 'show'
begin
if (params.has_key? :id) && (controller_name == 'subscriptions')
#subscription = Subscription.find(params[:id])
elsif user_signed_in?
#subscription = current_user.subscription || Subscription.new(email: current_user.email)
else
#subscription = Subscription.new
end
rescue ActiveRecord::RecordNotFound
#subscription = Subscription.new
flash.alert = "That subscription doesn't exist"
end
end
end
load_and_authorize_resource comes from cancancan.
my routes related to this test are:
resources :subscriptions do
member do
get 'confirm'
end
end
I don't really know where to go from here, so any advice would be appreciated.

Take a look at the stack trace for this exception:
SubscriptionsControllerTest#test_admin_can_not_view_subscriptions_that_don't_exist:
ActionView::Template::Error: No route matches {:action=>"edit", :controller=>"subscriptions", :id=>nil} missing required keys: [:id]
app/views/subscriptions/show.html.erb:13:in `_app_views_subscriptions_show_html_erb__1518678276755260966_70268849069860'
test/controllers/subscriptions_controller_test.rb:58:in `block (2 levels) in <class:SubscriptionsControllerTest>'
app/views/subscriptions/show.html.erb, line 13. Are you calling link_to (or a similar helper method) with a nil ID, maybe?

Look at your error message. It says the id parameter is missing. It is possible you are giving it a nil value. Because of this the router can't route the request properly.
Also, the error is for an request to the edit action, but the code you are showing is calling the show action. Can you clean up the code examples and error messages shown and make them consistent?

Related

BCrypt::Errors::InvalidHash in SessionsController#create when I try to sign in a user? (Ruby on Rails)

I get this error when I try to sign in a user, and can't figure out why. It's weird because when I run the following code I get the BCrypt Error, however when I change the find_by line (line 7) from can_email (candidate's email) to can_name (candidate's first name) I don't get the error at all, it just doesn't sign in the user presenting an "invalid password/email combination" error message on the webpage regardless if the combination is right or not. It's something to do with the password but I can't pin point it.
class SessionsController < ApplicationController
def new
end
def create
candidate = Candidate.find_by_can_email(params[:can_email])
if candidate && candidate.authenticate(params[:password]) **Error highlights this line**
session[:candidate_id] = candidate.id
redirect_to candidate
else
flash.now[:error]="Invalid email/password combination"
render 'new'
end
end
def destroy
if signed_in?
session[:candidate_id] = nil
else
flash[:notice] = "You need to log in first"
end
redirect_to login_path
end
end
Having the SessionController i am assuming you have a route as follows
# This is just a sample
post 'login' => "sessions#create" # gives login_path
Since there will be no session model i assume you have the form as follows
form_for(:session, url: login_path)
Now if you are collecting eg can_email and password you get
{session: {password: 'foo', can_email: 'foo#bar.com'}}
Accessing params[:session] returns the hash containing email and passowrd
So i think you should obtain them as follows
def create
candidate = Candidate.find_by(can_email: params[:session][:can_email])
if candidate && candidate.authenticate(params[:session][:password])
# login the user
else
# whatever
end
end
I got this error too, but in my case it was the result of myself having changed the encrypted_password value of my user in the database a while back and then forgetting about it.
This was easily fixed just by updating the password :)

How to update a profile attribute by link_to

I want to update a Profile model attribute by using link_to. The Profile model have a lang column, and I want to change to :en.
I could find out that I should use method: :put.
<%= link_to t('english'), profile_path(profile: {lang: :en}), method: :put %>
But it's ends up with error:
ActionController::UrlGenerationError in StaticPages#home
Showing /Users/ironsand/dev/phrasebook/app/views/layouts/_header.html.erb where line #21 raised:
No route matches {:action=>"update", :controller=>"profiles", :profile=>{:lang=>:en}} missing required keys: [:id]
I have this line in routes.rb to use the path:
resources :profiles, only: :update
How can I enable the function like this?
I found a similar question, but the case is a bit difference.
Edit
class ProfilesController < ApplicationController
def update
return redirect_to root_path unless current_user # If user is not logged in, redirect to /
if current_user.profile.update(profile_params) # Don't forget about validation for lang in Profile model
redirect_to root_path
else
redirect_to root_path
end
end
private
def profile_params
params.require(:profile).permit(:lang)
end
end
You need to identify the profile somehow, that's why it asks for id. But you can update profile without id, you just need to improve update method:
def update
return redirect_to root_path unless current_user # If user is not logged in, redirect to /
if current_user.profile.update(profile_params) # Don't forget about validation for lang in Profile model
redirect_to success_path
else
redirect_to error_path
end
end
private
def profile_params
params.require(:profile).permit(:lang)
end
For route, try this:
resources :profiles, only: [] do
collection do
put :update
end
end
or just:
put '/profiles' => 'profiles#update'
Since you're updating a specific profile, you need to supply something that lets your controller know what profile you're updating.
As you can see from the error message generated, your controller can't identify which profile it is that you're asking to be updated. You need to supply the id of the profile in order to update it.
One way this could be achieved with link_to is as follows:
link_to t('english'), profile_path(id: #profile.id, lang: :en), method: :put
lang would then be available in your update action in params[:lang].

Route Generation error for :create action in Minitest

I am currently writing functional tests for all of my controllers. For every single one, I can't get the create action to work. I keep getting the following error when I run rake test:
ActionController::UrlGenerationError: No route matches {:action=>"create", :comment=>{:content=>"I'm a comment.", :product_id=>"685617403"}, :controller=>comments}
Here is the action I am trying to test:
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#product =Product.find(params[:product_id])
#comment=Comment.create(params[:comment].permit(:content))
#comment.user_id= current_user.id
#comment.product_id= #product.id
if #comment.save
redirect_to product_path(#product)
else
render 'new'
end
end
end
Here is the route:
POST /products/:product_id/comments(.:format) comments#create
The following is my test:
def setup
#user=users(:chris)
#product=products(:banana)
end
test "should redirect destroy action if not signed in" do
post :create, comment: { content: "I'm a comment.", product_id:#product.id}
assert_redirected_to new_user_session_path
end
I can't figure out what I am doing wrong. I am fairly certain I am passing the correct params in.I've also tried it with and without a user_id: param and it still throws the same error. It works fine on the app, I've called params in the web console when making the request and it matches what I am passing. The only thing I am missing is the :id but I assumed that would be assigned when the comment was created in the test. Also there are no unique constraints which would prevent the created comment from being valid. My associations are in place and when it works on the app it saves to the database with the user_id and product_id. What am I missing?
I think you need to put product_id as its own first-level param too in order for the route to match up correctly. So try this instead:
post :create, product_id: #product.id, comment: { content: "I'm a comment.", product_id: #product.id }
Note that in your controller action, you reference params[:product_id] directly already, you don't reference params[:comment][:product_id]. Then, to reduce duplication, you can create the Comment as a child of that Product in your controller:
#comment = #product.comments.create(other params...)
Rails' routing errors can be extremely vague and unhelpful. 90% of the time the error boils down to some variation of this: a mismatched or misnamed ID, a nil value, etc.

Rails Api Rspec Test that takes params on route

routes.rb
get 'students/name_starts_with/:letter', to: 'students#name_starts_with'
get 'students/with_last_name/:last', to: 'students#with_last_name'
students_controller.rb
def name_starts_with
#students = Student.all.select{|s| s.first_name.start_with?(params[:letter]}
render json: #students.to_json
end
def with_last_name
#students = Student.all.select{|s| s.last_name == params[:last]}
render json: #students.to_json
end
students_controller_spec.rb
context '#name_starts_with' do
let!(:first_student){Student.create(first_name: 'John', last_name: 'Doe'}
it "starts with #{first_student.first_name.split('').first}" do
get :name_starts_with
expect(response.status).to eq(200)
expect(first_student.first_name.split('').first).to be('J')
end
end
context '#with_last_name' do
let!(:first_student){Student.create(first_name: 'John', last_name: 'Doe'}
it "has last name #{first_student.last_name}" do
get :with_last_name
expect(response.status).to eq(200)
expect(first_student.last_name).to be('Doe')
end
end
I have seeded bunch of student names. As far as I know both of these should be get routes/get requests. I am getting same error for both:-
Failure/Error: get :name_starts_with
ActionController::UrlGenerationError:
No route matches {:action=>"name_starts_with", :controller=>"students"}
# ./spec/controllers/students_controller_spec.rb:45:in `block (3 levels) in <top (required)>'
Should they be POST routes instead. Am I missing something. Am I doing whole thing wrong. Can somebody please help me solve this issue please.
I think this is route matching error. parameter letter is missing in this call.
Replace
get :name_starts_with
with
get :name_starts_with, letter: first_student.first_name.split('').first
I think this will fix your error.
I know this sounds stupid, but is your StudentsController inheriting from ApplicationController? (ApplicationController inherits from ActionController::Base
...and I'm not sure if you need the #students.to_json if you already declared render :json in your controller. just put render json: #students

Application variable is nil in testing environment

My application works fine, but I can't get a test to pass. I'm new at rails so forgive me if the answer is obvious.
I need a variable available to every view, so I'm doing this within application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :course
def course
#course = Course.find_slug(params[:course])
end
end
My test case looks like this:
it "creates an attempt" do
sign_in current_user
params = {:id => challenge.id, :description => "this was hard!", :course => "design"}
#course = FactoryGirl.create(:course)
post :completed, params
response.should redirect_to "/#{#course.slug}/?challenge_slug=" + challenge.slug
Attempt.count.should == 1
Attempt.last.description.should == params[:description]
end
The method within my controller looks like this:
def completed
#challenge = Challenge.find(params[:id])
#challenge.completed(current_user, params)
redirect_to "/#{#course.slug}/?challenge_slug=" + #challenge.slug.to_s
end
All this works fine if I'm using the application, but the test says:
1) ChallengesController completing a challenge creates an attempt
Failure/Error: post :completed, params
NoMethodError:
undefined method `slug' for nil:NilClass
# ./app/controllers/challenges_controller.rb:16:in `completed'
# ./spec/controllers/challenges_controller_spec.rb:36:in `block (3 levels) in <top (required)>'
If I hardcode my controller to say redirect_to "#{'expected_value'}" then the test passes, so it seems that within the testing environment I don't have access to the application variable #course, is this correct?
I'm lost on how to solve this. Any help is appreciated.
One solution is to stub the find method and return the instance variable.
before(:each) do
#course = FactoryGirl.create(:course)
Course.stub(:find_slug).and_return(#course)
end
This makes your tests more robust as the test for "find_slug" should be in your Course model, not the controller.

Resources