How can I redirect to login_path if email uniqueness validation fails? - forms

I am creating my very first Rails and MVC app. It is a website for my wedding guests to create their RSVPs.
I have a single form that is deeply nested. An RSVP has_one User and has_many Guests.
This form creates a User, RSVP, and Guests all in one go. There is also a link to edit an existing RSVP through a login_path.
I have validates_uniqueness_of :email on the User model. I would like to redirect to the login_path if a user attempts to create a new RSVP when they've already created one, i.e. the :email :uniqueness validation fails.
How can I redirect to the login_path if the :email :uniqueness validation fails?

You would need to test in your controller action if the unique validation failed for the that email and redirect if that's the case. That said, I'm not sure if there is better way to know if a certain validation failed for an specific field than just comparing the error message, as follows:
if #user.save
# do success actions
else
if #user.errors[:email] == 'has already been taken' # ugh
respond_to |format|
format.html { redirect_to login_path(email: #user.email) }
end
else
# handle other errors
end
end

Related

Can't get my form to be valid

I want to make a ReST infrastructure with a Symfony server (using FOSRestBundle).
there is
a Contact entity, (id, name, email)
a AddType form to add a new contact
On the client side, I have a form which sends a POST request whose body is
{"the_name_of_my contact_form":{"name":"foo", "email": "foo#example.org"}}
My controller (which extends FOSRestController) can see the data in the request
$request->request->get($form->getName()) returns {"name":"foo", "email": "foo#example.org"}
But whether I use $form->handleRequest($request) or $form->submit($data)
$form->isValid() is always false
I hope this is clear enough... can anyone help?
This problem is related to the CRSF validation. You have to disable it for the user requesting the service. You can disable it in you config.yml. You'll have to set something like this:
fos_rest:
disable_csrf_role: ROLE_API
Just make sure that the user requesting your service has this role. You can read more about user roles here.
Also, you'll have to submit you form, not handle it. Here is an snippet of it:
$form = $this->createForm(ProjectType::class, $project);
$form->submit($request->request->all());
if ($form->isValid()) {

Rails Tutorial - ERROR MESSAGE: expected response to be a <redirect>, but was <200>

The test in "Listing 8.20: A test for user logging in with valid information" is giving me a persistent error message:
FAIL["test_login_with_valid_information", UsersLoginTest, 1.051300315]
test_login_with_valid_information#UsersLoginTest (1.05s)
Expected response to be a <redirect>, but was <200>
test/integration/users_login_test.rb:22:in `block in <class:UsersLoginTest>'
The test is:
19 test "login with valid information" do
20 get login_path
21 post login_path, session: { email: #user.email, password: 'password' }
22 assert_redirected_to #user
23 follow_redirect!
24 assert_template 'users/show'
25 ....
The Sessions controller is what I am imagining is causing this error message
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user # What I want to happen
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new' # What is happening
end
end
If the user is authenticated, it should redirect to the users profile page
else the login form will render
So the problem I believe is that my test user is not being authenticated. The application login and redirect functions just fine when I login manually on the site.
test/fixtures/users.yml has this to pass the password 'password'
password_digest: <%= User.digest('password') %>
and the user model has this to digest the password 'password'
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
Where does the problem lie? and further more how can I make another test that shows me why this test is failing?
I had a similar (identical) issue if I'm reading this right and for the sake of clarity for future browsers I'll add the following: As Shonin pointed out the issue has to do with the controller taking downcase email addresses and so apparently in users.yml the code
michael:
name: Michael Example
email: Michael#example.com
password_digest: <%= User.digest('password') %>
Will not pass while
michael:
name: Michael Example
email: michael#example.com
password_digest: <%= User.digest('password') %>
Will pass.
after a good nights sleep I realized it was likely due to a database error. Low and behold my database had a capitalized email and my controller submits a downcased email.
Solved.

First argument in form cannot contain nil or be empty in rails 4.0

There are tons of these, especially for Rails 4.0 and Rails 3.x. I am new to nested routing and I find it very difficult, especially with forms.
So I have the following:
Routes
resources :users do
resources :api_keys, path: '/developmentcenter'
end
The relationship here is: user has many api keys where api key belongs to user.
Controller
class ApiKeysController < ApplicationController
before_action :authenticate_user!
def new
#user = User.find(params[:user_id])
#api_key = ApiKey.new(:user => #user)
end
def index
#user = User.find(params[:user_id])
#api_key = #user.api_keys
end
def create
#user = User.find(params[:user_id])
#api_key = ApiKey.new(create_new_api_key)
create_api_key(#api_key, #user)
end
def destroy
#user = User.find(params[:user_id])
destroy_api_key(#user)
end
private
def create_new_api_key
params.permit(:api_key, user_attributes: [:id])
end
end
The above is pretty basic. create_api_key is a method that does something on save, or does something else on failure to save. While destroy_api_key Just find the api key based on the user id and deletes it, does something on success and something on failure.
So now lets create a form - which has a single button for creating the API key.
<h2>Create a new Key</h2>
<%= form_for ([#user, #api_keys]) do |f| %>
<p class="button"><%= f.submit "Generate API Key" %></p>
<% end %>
All we need is a single submit button that upon click, creates a new api key for the user whom is logged in.
But wait we have a an error:
First argument in form cannot contain nil or be empty
This error takes place at:
<%= form_for ([#user, #api_keys]) do |f| %>
So - I have looked at every single one of the stack questions (well most) that deal with this error, I have changed my controller based on a few, to what you see above. I have even look at the form helpers docs in the rails manual.
I cannot figure this out.
It is telling you that #user is empty or nil in the context it is using. Either this is a user who has not been created in the DB yet, or your User.find call is not working to return an actual user. The form needs to know who #user is before it can create a nested resource (#api_key) for it. Your `create_api_key' is completely wrong. You need to whitelist your params first, then find the user in the DB (or create them), then you can use the #user instance variable to create a form for that user to create a key. I think if you do it right, you shouldn't need to call #api_keys in the beginning of the form if you defined the relationships in your models (has_many or has_one, belongs_to etc.). Can you post the web server console output when you visit that page? First off you are calling
#user = User.find(params[:user_id])
every time in your controller. You should DRY that up with a before_action.
private
def set_user
#user = User.find(api_key_params[:user_id])
end
Then at the top of the controller you would have:
class ApiKeysController < ApplicationController
before_action :authenticate_user!
before_action: :set_user
Also you should make your .permit a method that returns a variable called api_key_params:
def api_key_params
params.require(:user).permit(:api_key)
end
That way you have the things you want returned in a whitelist. To access these params you just call the method. You can specify a param you want returned from the method like my set_user example. But it also gives you the ability to do things like:
def create
#api_key = Api_key.new(api_key_params)
end
That way the Api_key.new gets the user and creates the key. You don't show how or where you generate the key itself. If it gets created by some method, you should put that in the model for Api_key. There are some other things about your code that are confusing without seeing the rest of your files.

Zend -> ZfcUser create new user after email verification/activation

I am working on a website, which already has a working registration form, using the ZfcUser module.
However,
I need to also be able to create a user via the admin page i've created.
Step by step it goes something like this:
Admin adds user by filling in first name, last name and email.
email gets sent to user.
user clicks validation link and gets redirected to website.
now the user only has to enter his desired password and he is done.
How would i be able to do this, if at all possible?
first of all, im not sure what would be the best aproach, but a few come to my mind.
I think the easier would be to load the register form in your admin, remember you can load it from any controller with the service manager, something like
$form = $sm->get('zfcuser_register_form');
and then you can work with it as you would do with any form, sending it to the view, and so.
You would have the full register form, with all the fields you have set as required in your zfcuser.global.php, including the password. I think it is good to set a temp password, and have the user change it later. also you could have its status as unconfirmed until the first password change.
If you dont want an specific field, you can take it out as you would with any form, by means of
$form->remove('element_name');
You would want to check the element names at ZfcUser\Form\Register
Also, remember that if you remove any field, you would have to modify the input filter, otherwise the validation will fail. For this, in your module's bootstrap, you should attach an event listener, something like this:
$em = $e->getApplication ()->getEventManager ();
$em->attach ( 'ZfcUser\Form\RegisterFilter', 'init', function ($e) {
$filter = $e->getTarget ();
//now modify the inputfilter as you need
});
Then, you will have to send the mail to the user. For that i will also use the event manager, at your bootstrap you register a listener for when the user is created, this is by means of
$sm = $e->getApplication ()->getServiceManager ();
$zfcServiceEvents = $sm->get ( 'zfcuser_user_service' )->getEventManager ();
$zfcServiceEvents->attach ( 'register.post', function ($e) {
$form = $e->getParam ( 'form' );
$user = $e->getParam ( 'user' );
//now you have all the info from the form and the already created user, so you can send the mail and whatever you need.
The last step, is to let the user change his password. To do this, i will send him to a module where you show the change password form, that you can retrieve with:
$sm->get('zfcuser_change_password_form');
or directly, sending him to the /user/change-password url that is one of the predefined with zfc-user.
I think this will be the cleanest way.
Another approach
If you dont like it that way, you can use another approach where you create your own form, fill it, save the data to a temp table, send the mail and then...when the user comes to set his password, you build a register form, with the fields pre-filled (and hidden, changing the input type to hidden, or by css) and let him send the form, so while he thinks he is sending just the password, actually he is sending all the registration form, and from here everything is like in normal registration.
For this solution you will also have to use the events, but probably you'd have to take a look at the register event,that is triggered when the form is sent, before the user is saved in the database, so you can modify any data you could need.
$zfcServiceEvents->attach ( 'register', function ($e) {
$form = $e->getParam ( 'form' );
And also you should take a look to the already mentioned init event, where you can retrieve the form before you show it to the user, and prefill any data from the temp table.
$events->attach ( 'ZfcUser\Form\Register', 'init', function ($e) {
$form = $e->getTarget ();
//now you set form element values from the temp table
Probably this is so confusing, but i hope you at least get a clue of where start from!

why facebook email is not retrieved?

After user accept to sign in with his facebook account, my app callback will be called by facebook, I've noticed that facebook didn't retrieved user email, although in the configuration of omniauth, I've listed email in the permission list like this:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, omniauth_app_id, omniauth_app_secret_id,
:scope => 'email,user_birthday,read_stream', :display => 'popup'
end
I am expecting to find email in request.env['omniauth.auth']['extra']['raw_info']['email'] or request.env['omniauth.auth']['info']['raw_info']['email']
But, its not there .. in fact, its not in any attribute within the request object!
Any idea ? is it related to my app facebook settings ?
EDIT
Here is the call back result from Facebook:
puts auth.inspect
#<OmniAuth::AuthHash credentials=#<Hashie::Mash expires=true expires_at=1353164400 token="***"> extra=#<Hashie::Mash raw_info=#<Hashie::Mash first_name="***" id="***" last_name="***" link="***" locale="ar_AR" name="***" timezone=2 updated_time="2012-11-17T13:01:59+0000" username="***" verified=true>> info=#<OmniAuth::AuthHash::InfoHash first_name="***" image="***" last_name="***" name="***" nickname="***" urls=#<Hashie::Mash Facebook="***"> verified=true> provider="facebook" uid="***">
I have replaced reall data with *, but, you can see that the email data is missing ..
EDIT2
Here is my gems used at Gemfile
gem 'devise'
gem 'omniauth'
gem 'omniauth-facebook', '1.4.0'
gem 'oauth2'
Ok, I found it! Here is what facebook says here
By default, calling FB.login will attempt to authenticate the user
with only the basic permissions. If you want one or more additional
permissions, call FB.login with an option object, and set the scope
parameter with a comma-separated list of the permissions you wish to
request from the user.
So, I have to add 'email' like this:
FB.login(function(response) {
// handle the response
}, {scope: 'email,user_likes'});
Thanks very much for all people who tried to help :-)
It's possible to have an facebook account without confirm the email. In this case, the request.env['omniauth.auth']['info'] has no 'email' key.
We use devise and omniauth for facebook - following the following railscast. I don't know if you can adapt but we have successfully pulled the email and some other info. from the callback.
http://asciicasts.com/episodes/241-simple-omniauth
Our controller action looks like this (condensed):
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create(:provider => omniauth ['provider'], :uid => omniauth['uid'])
redirect_to authentications_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
User.apply_omniauth:
def apply_omniauth(omniauth)
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
end
We get the email, as advise by someone else, using this:
#omniauth_email = session[:omniauth][:info][:email]
I hope this helps, I know it's not quiet what you're looking for.
-- edit --
My initialiser is different to yours. Try removing all that xs stuff and use something like this:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, 'xxxxxx', 'xxxxxx'
...
end
How about fetching it from the info hash?
request.env['omniauth.auth']['info']['email']
I think that it is what omniauth-facebook recomends in the Auth Hash section