How do you tweak the arguments that get passed in rspec's (v 2.6.1) post :create? Pointing me to rspec documentation that explains this command and how to use it would be fine with me. I've gone looking but can't find anything that answers my question.
I'm having difficulty writing rspec to behave properly for my Customer :create action that receives parameters, both for the Customer, as well as a different User object. More precisely, I'm unable to get the values in the hash I pass in the rspec post :create to behave like the input that comes from my form.
Please note that my Customer and User objects are separate ActiveRecords, and each Customer :belongs_to a User.
Here's my RSpec that's not doing what I want it to:
describe CustomersController do
render_views
describe "for signed-in admins" do
before(:each) do
#customer_attr = Factory.attributes_for(:customer)
#admin = Factory(:admin)
test_sign_in(#admin.user)
end
describe "POST 'create'" do
describe "success" do
it "should create a customer" do
lambda do
post :create, :customer => #customer_attr # The problem is here
end.should change(Customer, :count).by(1)
end
end
end
end
end
Here's the failure:
1) CustomersController for signed-in admins POST 'create' success should create a customer
Failure/Error: lambda do
count should have been changed by 1, but was changed by 0
# ./spec/controllers/customers_controller_spec.rb:323:in `block (5 levels) in <top (required)>'
The problem (as you'll see below) is obviously that post :create, :customer => #customer_attr doesn't include an argument that will end up in params[:user][:email], so the #user.save is failing because params[:user] is empty, but I've tried all kinds of ways to recompose the hash and pass it in that line but can't get it to work.
Here's the relevant controller code:
def create
#user = User.new(params[:user])
#user.user_type = 'customer'
#customer = Customer.new(params[:customer])
if #user.save
#user.customer = #customer
if #customer.save
# create stuff
redirect_to customer_path, :flash => { :success => "Customer created." }
else
# failure stuff
end
else
# more failure stuff
end
end
Here's the relevant form code (which behaves how I want it to):
<%= form_for(#customer) do |f| %>
<%= fields_for :user do |user_form| %>
<%= user_form.label :email %>
<%= user_form.text_field :email %>
<% end %>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
<%= f.label :last_name %>
<%= f.text_field :last_name %>
<%= f.submit "Create Customer Account" %>
<% end %>
Lastly, here's my factories code:
factory :user do |f|
f.sequence :email do |n|
"person#{n}#example.com"
end
f.password "thepassword"
f.password_confirmation "thepassword"
end
factory :customer do |f|
f.first_name "First"
f.last_name "Last"
f.user
after_build { |customer| customer.user.user_type = 'customer' }
end
The solution I found was to compose my hash to make it look exactly how I wanted, then pass it directly, without the :customer => in front of it.
before(:each) do
#customer = Factory(:customer)
#customer_attr = Factory.attributes_for(:customer)
#user_attr = {:user => Factory.attributes_for(:user)}
#customer_and_user_attr = {:customer => #customer_attr }.merge(#user_attr)
#admin = Factory(:admin)
test_sign_in(#admin.user)
end
Then in my controller I access items as e.g. params[:customer][:first_name] or params[:user][:email]
And the rspec looks like:
post :create, #customer_and_user_attr
So if I understand your question correctly, you just need to pass in another key to post, namely :user => #admin.user. Did I misunderstand you?
Related
I have a new application that the root user create another users, when i create the user with the model.
#user = User.new(:email => params[:email], :password => params[:password], :password_confirmation => params[:password_confirmation])
#user.save!
The users saves "correctly" on the table on "encrypted_password" the value and the others fields too, but when i try to login i have an error
"Invalid Email or Password" so when i read the record the password is.
encrypted_password: $2a$11$wFnpiA.l9HezNXfnAGkttuu2IGIXByETytLrEkdDsa8sBFrc8Bdmq
But i used another password the root password that actually works.
But on the table is :
encrypted_password: $2a$11$VKOAUk5pjILU1QHYmkpJSem9KKm70QJPS7Oj.nPM/pTuyu1tqZaQO
So, my model of the devise is not saving the correct encryption of the password.
My TestController:
class UsersController < ApplicationController
before_action :authenticate_user!
def index
#Users = User.all
end
def create
begin
#user = User.new(:email => params[:email], :password => params[:password_first], :password_confirmation => params[:password_confirmation])
#user.save!
flash[:notice] = "Usuario creado correctamente"
redirect_to action: 'index'
rescue Exception => e
flash[:alert] = "Error al crear al Usuario: " + e.message
redirect_to action: 'index'
end
end
end
What am i doing wrong?
Regards
BCrypt goes out of its way to scramble the password as much as possible to make it extremely difficult to reverse that hashing operation. For any given input string to BCrypt::Password.create there are around 680 trillion-trillion-trillion possible output strings (2128), so it's unlikely you'll ever get the same one twice:
hashes = 10.times.map { BCrypt::Password.create('test') }
# => [
# "$2a$10$vMrgjJHqvwnEKIs0fZ76pO3gbWL/0C3ExqK9HOpi/mHYu2.4GAO2K",
# "$2a$10$KxBOarDzRPHp7QF1GGqNnuplRs1B5rNVfp21IHx1/HzQ0YIcIkLRW",
# "$2a$10$emCdZAA.GU8GwQZkeJLfAuUTY2aEnhFmZ.GQAhDpJ.JGSh/m6s/k2",
# "$2a$10$6R6xmGyK7Tb1MKsQb00vpOJKwpi56aj98JLoBJhBN4vWSQb7zagQm",
# "$2a$10$r4qmb.C.vm88pL2nJK5TdOaWIboYaO6a1xHIRH.QDER6qYR6Ajvo.",
# "$2a$10$mlVWz4IHTgYHSf3tAgEgpenpDHtGWYev4EUENLs7hnLlm6ikPhUxy",
# "$2a$10$ixXdZZuc9rIVAozO8tyq5.wlsVOWBc6QWetNh3PvjPj2pGlqh.XOy",
# "$2a$10$zLzuevtOl.g4RbaHpdeTZ.k4qjE/1m4nh6gN4mhcIKQPSa5sBcG5u",
# "$2a$10$F/F71.DYEuzxS4W0w5m/a.IRpaVJxeh9sKUJ7DyQb5xU3SvFu1Ib.",
# "$2a$10$ILXg8R52ZtHHbQbT0FxSFOj8YNqpNLmrH.6FhM3RGMwIuBeP1YXHa" ]
This is alright though since the verification routine can handle checking password:
hashes.map { |h| BCrypt::Password.new(h) == 'test' }
# => [true, true, true, true, true, true, true, true, true, true]
They all match. It's important to note that verifying ten passwords does take a small but noticeable amount of time. This is what make BCrypt particularly well suited to password storage: Guessing is expensive so throwing huge dictionaries of words at a hash to see which match is extremely difficult. This is not the case with weaker hashes like MD5 or SHA1 where a million operations per second is completely feasible. BCrypt is deliberately about a half million times slower.
Start with a regular decent Rails crud setup and see if the issue does not solve itself.
# routes.rb
resources :users, only: [:create, :index]
class UsersController < ApplicationController
before_action :authenticate_user!
# GET /users
def index
#Users = User.all
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
flash[:success] = "Usuario creado correctamente"
redirect_to action: :index
else
render :new
end
end
def user_params
params.require(:user)
.permit(:email, :password, :password_confirmation)
end
end
# app/views/users/_form.html.erb
<% form_for(#user || User.new) do |f| %>
<% if f.object.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(f.object.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul>
<% f.object.errors.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email %>
</div>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</div>
</div>
<% end %>
# app/views/users/new.html.erb
<%= render partial: 'form' %>
You can embed the partial in your root view or the users index by:
<%= render partial: 'users/form' %>
Note that you should render and not redirect if the record is invalid. If you redirect the users input and any validation messages you want to show are gone.
In my Rails app, if a user wants to delete his own account he will first have to enter his password in my terminate view:
<%= form_for #user, :method => :delete do |f| %>
<%= f.label :password %><br/>
<%= f.password_field :password %>
<%= f.submit %>
<% end %>
This is my UsersController:
def terminate
#user = User.find(params[:id])
#title = "Terminate your account"
end
def destroy
if #user.authenticate(params[:user][:password])
#user.destroy
flash[:success] = "Your account was terminated."
redirect_to root_path
else
flash.now[:alert] = "Wrong password."
render :terminate
end
end
The problem is that I can't seem to find a way to test this with RSpec.
What I have is this:
describe 'DELETE #destroy' do
before :each do
#user = FactoryGirl.create(:user)
end
context "success" do
it "deletes the user" do
expect{
delete :destroy, :id => #user, :password => "password"
}.to change(User, :count).by(-1)
end
end
end
However, this gives me an error:
ActionView::MissingTemplate:
Missing template users/destroy, application/destroy with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder]}. Searched in:
* "#<RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator:0x007fa7f51310d8>"
Can anybody tell me what I'm missing here or suggest a better way to test this action?
Thanks for any help.
OK, this is my solution:
describe 'DELETE #destroy' do
context "success" do
it "deletes the user" do
expect{
delete :destroy, :id => #user, :user => {:password => #user.password}
}.to change(User, :count).by(-1)
end
end
end
The before :each call I had before was useless (this is not an integration test after all). The password has to be passed in like this: :user => {:password => #user.password} which I didn't know until reading this thread.
for now i've got followings:
model => User (name, email)
has_and_belongs_to_many :trips
model => Trip (dest1, dest2)
has_and_belongs_to_many :users
validates :dest1, :dest2, :presence => true
model => TripsUsers (user_id, trip_id) (id => false)
belongs_to :user
belongs_to :trip
As you see from the code, trip model has validation on dest1, and dest2, but it's not showing up an errors. Controller and view defined as follow:
trips_controller.rb
def new
#user = User.find(params[:user_id])
#trip = #user.trips.build
end
def create
#user = User.find(params[:user_id])
#trip = Trip.new(params[:trip])
if #trip.save
#trip.users << #user
redirect_to user_trips_path, notice: "Success"
else
render :new
end
end
_form.html.erb
<%= simple_form_for [#user, #trip] do |f| %>
<%= f.error_notification %>
<%= f.input :dest1 %>
<%= f.input :dest2 %>
<%= f.submit "Submit" %>
<% end %>
According to the rails guide on presence validation, it can't be used with associated objects. Try to use a custom validation:
validate :destinations_presence
def destinations_presence
if dest1.nil?
errors.add(:dest1, "missing dest1")
elsif dest2.nil?
errors.add(:dest1, "missing dest2")
end
end
This is my first time doing validation on a rails application. I saw many tutorials which made it seem easy. I don't know why I cant get it to work.
Below is my setup.
Controller Admin (action = login)
def login
session[:user_id] = nil
if request.post?
#user = User.authenticate(params[:userId], params[:password])
if true
session[:user_id] = #user.user_id
flash.now[:notice] = "Login Successful"
redirect_to(:controller => "pages", :action => "mainpage")
else
flash.now[:notice] = "Invalid user/password combination"
end
end
end
So first time user comes to admin/login they are just presented with a form below
login.erb.html
<% form_for :user do |f| %>
<p><label for="name">User ID:</label>
<%= f.text_field :userid %>
</p>
<p><label for="password">Password:</label>
<%= f.password_field :password%>
</p>
<p style="padding-left:100px">
<%= submit_tag 'Login' %>
</p>
<% end %>
My User model:
class User < ActiveRecord::Base
validates_presence_of :userid, :password
def self.authenticate(userid, password)
user = self.find_by_userid_and_password(userid, password)
user
end
end
Actual field names for userId and password in my DB: userid password
I am expecting behavior that when user does not enter anything in the fields and just clicks submit. it will tell them that userid and password are required fields. However, this is not happening
From the console I can see the messages:
>> #user = User.new(:userid => "", :password => "dsf")
=> #<User id: nil, userid: "", password: "dsf", created_at: nil, updated_at: nil>
>> #user.save
=> false
>> #user.errors.full_messages
=> ["Userid can't be blank"]
So error is somewhere in my form submit...
UPDATE: validations only happen when u SAVE the object....here I am not saving anything. So in this case I have to do javascript validations?
It's the if true line. Change it to
if #user = User.authenticate(params[:userId], params[:password])
or
#user = User.authenticate(params[:userId], params[:password])
if #user
...
end
I'd also add redirect_to login_path to the failure case.
You can also slim down your auth method:
def self.authenticate(userid, password)
find_by_userid_and_password(userid, password)
end
It turns out, there are several issues here, and I'll try to cover them all. Let's start with your model:
class User < ActiveRecord::Base
validates_presence_of :userid, :password
def self.authenticate(userid, password)
self.find_by_userid_and_password(userid, password)
end
end
The validation doesn't come into play for logging in, only for creating and updating user records. The authentication has been trimmed, because ruby automatically returns the last calculated value in a method.
Next, the login action of your controller:
def login
session[:user_id] = nil
if request.post?
if #user = User.authenticate(params[:userId], params[:password])
session[:user_id] = #user.user_id
flash[:notice] = "Login Successful"
redirect_to(:controller => "pages", :action => "mainpage")
else
flash.now[:error] = "Invalid user/password combination"
end
end
end
Notice we don't use flash.now on a redirect - flash.now is only if you're NOT redirecting, to keep rails from showing the message twice.
Finally, you shouldn't be using form_for, because this is not a restful resource form. You're not creating or editing a user, so use form_tag instead:
<% form_tag url_for(:controller => :users, :action => :login), :method => :post do %>
<%= content_tag(:p, flash[:error]) if flash[:error] %>
<p><label for="name">User ID:</label>
<%= text_field_tag :userid %>
</p>
<p><label for="password">Password:</label>
<%= password_field_tag :password%>
</p>
<p style="padding-left:100px">
<%= submit_tag 'Login' %>
</p>
<% end %>
This will do what you want. This is a great learning exercise, but if you're serious about user authentication in a production application, checkout rails plugins like restful_authentication or clearance that do this for you in a much more sophisticated (and RESTful) way.
I have a very basic rails app. I am playing around with validation.
Controller
class PagesController < ApplicationController
def new
#user = User.new
end
def edit
#user = User.new(:state => params[:state], :country => params[:country])
#user.save
end
end
Model
class User < ActiveRecord::Base
validates_presence_of :country
validates_presence_of :state
end
Views/pages/edit.html.erb
<% form_for :user, #user, :url => { :action => "edit" } do |f| %>
<%= f.text_field :country %>
<%= f.text_field :state %>
<%= submit_tag 'Create' %>
<% end %>
All I want to do is click Create when I have not entered anything and then have a validation come up and list the required fields. I've read some tutorials and they make it so simple. Why can't I get this to work? what am i doing wrong? When i create a scaffold then it works ok but that generates a scaffold.css in public/stylesheets. W/out scaffold right now i have no stylesheet in the public folder.
you're sending the form to the "edit" action, which doesn't do any processing. You need it to go to the "create" action, which should look something like this:
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = 'Your user was successfully created.'
redirect_to users_path
else
render :action => 'edit'
end
end
Your form_for line can be short and sweet. Also, you need to call error_messages to get the auto-generated list of errors:
<% form_for #user do |f| %>
<%= f.error_messages %>
...other fields go here...
<% end %>
See Rails conditional validation: if: doesn't working
It seems like you think validates ... if: works differently as it actually does. This line
validates :to_id, presence: true, if: :from_different_to?
translates to validate that the to_id is present if the from_different_to method returns true. When from_different_to evaluates to false then do not validate.