I'm new to rails and I want to offer users the feature to change password in their user page. Requiring their old password and setting a new one. However, I have no idea how to accomplish this.
There is a Railscasts episode on resetting password through email but I dont want to do it with email.
I generated a Password Update controller. But I know I am making a terrible mistake. Hopefully you guys can point it out. And hopefully this question wasn't too confusing.
password updates controller
class PasswordUpdateController < ApplicationController
def new
end
def update
end
def show
#user = User.find(params[:id])
end
end
new password_update
%h1
= form_for #user, :url => password_update_path(params[:id]) do |f|
.field
= f.label :old_password
= f.password_field :password
.field
= f.label :password
= f.password_field :password
.field
= f.label :password_confirmation
= f.password_field :password_confirmation
.actions
= f.submit "Update Password"
Routing Error No route matches [POST] "/password_update/1"
routes.rb
TootApp::Application.routes.draw do
get "sessions/new"
get "static_pages/home"
get "static_pages/help"
get "password_updates/new"
resources :sessions
resources :products
resources :photos
resources :password_update
That's not how you should use controllers. Password update should be an 'action' within a 'controller' and that controller, when it comes to user credentials, should rightfully be in the UsersController, where an 'update' action takes in parameters that you post from a form:
class UsersController < ApplicationController
def update
#user = User.find(params[:id])
#user.update_attributes(params[:user])
....
end
end
And in your HTML form, you can just specify:
= form_for #user do |f|
...
without even needing to specify the URL, since rails will implicitly provide you with the right URL in the background :)
And make sure to have your 'Routes' correctly setup like so:
resources :users
It basically sets up the RESTful routes for users. You can find out the routes it generates, by running the following in your console:
rake routes
Apart from hooking up your own user credential management features, why not try out the devise gem by Jose Valim, and pair it with SimpleForm gem.
https://github.com/plataformatec/devise
https://github.com/plataformatec/simple_form
Hope this helps!
Related
I have a checkbox (like a terms of use) that I need to be checked every time a user signs in.
I've seen some examples on adding a checkbox on the sign up page, adding a virtual attribute to the User model, etc.
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
%p
= f.label :username, 'Username'
= f.text_field :username
%p
= f.label :password
= f.password_field :password
%p
%span
= check_box_tag :terms_of_use
I have read the
= link_to 'Terms of Use', '#'
%p
= f.submit 'Sign in'
Here's my devise route:
devise_for :users, controllers: { sessions: 'sessions' }
And here's the custom controller:
class SessionsController < Devise::SessionsController
def create
if params[:terms_of_use]
super
else
# Not sure what to put here? Is this even the right track?
# Also, redirect the user back to the sign in page and let
# them know they must agree to the terms of use.
end
end
end
How would I go about requiring the checkbox to be selected every time a user signs in?
This blog post may help: http://hollandaiseparty.com/order-of-abstractcontrollercallbacks/
Adding a prepend_before_action should allow you to check for the terms_of_use and redirect if needed before allowing Devise to take over. Something like:
class SessionsController < Devise::SessionsController
prepend_before_action :check_terms_of_use, only: [:create]
def check_terms_of_use
unless params[:terms_of_use]
# Since it's before the session creation, root_path will take you back to login
redirect_to root_path
end
end
end
I'm trying to split the users edit page (app/views/devise/registrations/edit.html.erb) into 2 pages for better UI, like:
/settings
/settings/profile
I'm fairly new to Rails, did Michael Hartl's tutorial and had read a few more I got my hands on, just building my first application, even if I have some experience with php
This is the view I try to split in 2, it is a view provided by the Devise gem (app/views/devise/registrations/edit.html.erb)
<h2>Login Details</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<%# sensitive info %>
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %>
....
<%= f.label :current_password %> <i>(to confirm changes)</i><br />
<%= f.password_field :current_password, autocomplete: "off" %>
<h2>Profile Details</h2>
<%# non-sensitive info %>
<%= f.label :name %><br />
<%= f.text_field :name %>
<%= f.submit "Update" %>
<% end %>
It uses a custom RegistrationsController (app/controllers/registrations_controller.rb)
class RegistrationsController < Devise::RegistrationsController
def update
....
end
end
Further more, this view is accessed via this route:
edit_user GET /users/:id/edit(.:format) users#edit
My main question is, how do I split this page into 2:
/settings containing Login Details
/settings/profile containing Profile details
and both to processed by the same controller, or the same action
Do I need to create a new controler/route/view, like:
controller: SettingsProfile
route: get 'settings/profile' => 'settings_profile#new'
view: app/views/settings_profile/new.html.erb
If so how do I pass the view the "resource" information, or any information for the matter of fact:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
Things are pretty fuzzy at this point, please bear with me on this one
You don't need a separate controller, especially since you're already extending the default Devise RegistrationsController, which already works fine for updating user attributes.
Edit: If these aren't just extended user attributes, and profile is it's own object with its own logic and behaviour, then consider creating it's own controller, to manage the CRUD for that object.
If you're using devise's user/edit page as part one, all you need to do is add a profile action in your custom controller, and create a view file to go with it.
# this is all that's in the edit action from Devise
def edit
render :edit
end
# add this to your custom RegistrationsController
def profile
render :profile
end
Then you can fiddle with your routes (see this and this) until they route the URLs you want to use to the correct controller:
# you probably have this, which covers your current user/edit route
devise_for :users
# but you can add this to extend these routes
devise_scope :user do
# will route /profile to the profile action on User::RegistrationsController
get :profile, to: 'users/registrations'
# or if you want more control over the specifics
get 'users/settings/profile', to: 'users/registrations#profile', as: :user_profile
end
For your second view/form to update user attributes from another, non-devise controller, you can use form_for current_user, { url: user_registration_path }
If you do want to use resource, you'll have to add this to the top of your registrations controller, so that the resource gets defined on your profile action as well:
prepend_before_filter :authenticate_scope!, only: [:edit, :profile, :update, :destroy]
Take a look at devise's documentation around strong parameters to see how to make sure whatever additional attributes you're going to add to your user are white listed by your custom RegistrationsController.
As you suggested, one method would be to create a new controller, route, and view to handle this.
I might create a UserProfilesController controller with two actions: UserProfilesController#show and UserProfilesController#edit.
Then as you suggested, a route, e.g.,
get 'user_profiles/:id' => 'user_profiles#show'
get 'user_profiles/:id/edit' => 'user_profiles#edit'
In Devise parlance, the resource refers to the user. So the :id being passed above must be a User id of course. If you don't want to do that, you could always just assume you meant the current_user in which case you can skip using :id in the routes and just retrieve it in the controllers via current_user.id.
Finally, you just have to split out the profile details from the Devise view and create some under app/views/user_profiles/new.html.erb and similarly for edit.html.erb. Remember to remove the profile bits from the Devise view and I think you're on your way.
An Addendum
#AmrNoman made a good suggestion re: the update method. If you are following with my solution, you would add another action UserProfilesController#update, and a new route in your routes.rb file:
put 'user_profiles/:id/update' => 'user_profiles#update'
Additionally, if you intend to later refactor User to remove the profile details and handle them in a separate model, it may be prudent to replace my references to :id in the above code to :user_id. In this way, if you at some point create, e.g., a model called UserProfile it will be clearer that the :id is not the UserProfile#id but the UserProfile#user_id foreign key. This will leave you the ability to use :id to refer to UserProfile.id directly without affecting any API consumer in your app.
It may be a bit overkill but I think it's good practice.
Chris Cameron's answer is right, but I think this is closer to what you want:
First create your routes in routes.rb:
# this gets the edit page for login details
get "settings" => "user_profiles#edit_credentials", as: "edit_credentials"
# this gets the edit page for other profile info
get "settings/edit" => "user_profiles#edit_profile", as: "edit_profile"
# update method shared by both forms
# (you can create another one to handle both forms separately if you want)
put "settings" => "user_profiles#update"
Then your controller user_profiles_controller.rb:
class UserProfilesController < ApplicationController
# fill the methods as you need, you can always get the user using current_user
def edit_credentials
end
def edit_profile
end
def update
end
end
and finally your views, first views/user_profiles/edit_credentials.erb.html, here you show form for login details:
<%= form_for(current_user, url: settings_path) do |f| %>
<% end %>
then same thing in views/user_profiles/edit_profile.erb.html, just change your form contents:
<%= form_for(current_user, url: settings_path) do |f| %>
<% end %>
There might be some errors, but hopefully this gets you in the right direction (also make sure to handle authentication and authorization).
I have an object that I am trying to allow users to edit in my rails 4 app. The user has_one supp_form and I want them to be able to edit the information in the supp_form. The page is loading fine and the relationships are setup properly.
The error
No route matches [PATCH] "/businesses/3/supp_form/edit"
when I rake routes I see the following route:
edit_business_supp_form_path GET /businesses/:business_id/supp_form/edit(.:format) supp_forms#edit
GET /businesses/:business_id/supp_form(.:format) supp_forms#show
PATCH /businesses/:business_id/supp_form(.:format) supp_forms#update
PUT /businesses/:business_id/supp_form(.:format) supp_forms#update
supp_forms_controller.rb
class SuppFormsController < ApplicationController
before_filter :authenticate_user!
def new
#suppform = SuppForm.new(supp_form_params)
end
def create
#suppform = SuppForm.create(supp_form_params)
end
def edit
#user = User.current_user
#suppform = #user.supp_form
end
def update
#user = current_user
#suppform = SuppForm.update(supp_form_params)
end
private
def supp_form_params
params.require(:supp_form).permit(:id, :business_id, :title, :first_name,
:last_name, :applicant_role, :work_phone_number)
end
end
View
<%= form_for #user.supp_form, :url => edit_business_supp_form_path(#user.supp_form), :html => { :class => "sky-form", :id => "sky-form4" } do |supp_form| %>
<%= supp_form.text_field :work_phone_number, :placeholder => "Your new phone number" %>
<% end %>
The problem is that it tries to access the route using a PATCH request, that is used for updating. In your routes the /businesses/:business_id/supp_form/edit route is only specified for GET requests, thus the error.
This happens because the path you are using in the form points to the edit action (which is only responsible for showing the edit form) and should instead point to the update action. So the route you should be actually using in the is the supp_form_path that, in connection with the PATCH method, pushes the information to the update action, where the object is updated.
This is for Rails 4.04 and Ruby 2.1. I'd like to allow my users to have addresses. So I generated an address table and gave it columns (NUMBER, STREET, CITY, STATE). Now when I go to the following url, I'd like to be able edit this information:
webapp.com/users/edit/
However I noticed it only showed the same old information (name, password, email). So I went to the view and added simple_fields for my new relationship so the view now looks like this:
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<div class="form-inputs">
<%= f.input :email, required: true, autofocus: true %>
<%= f.input :name, required: false %>
<%= f.simple_fields_for :addresses do |a| %>
<%= a.input :number %>
<%= a.input :street %>
<%= a.input :city %>
<%= a.input :state %>
<%= a.input :country %>
<% end %>
<%end%>
However it still doesn't generate the fields needed for address. I think this is because none of my users currently have any addresses attached to their account profile (because this migration was just created). However, in this case there should be blank fields generated so I can ADD address information.
I feel like I need to do something in the Users#Edit action like this
#users.each do |user|
user.address.build
end
Is that right? How can I override the users controller because this was created by Devise and I don't actually have a users controller (I looked for it it and couldn't find it).
UPDATE
Ok, I'm getting closer. I had to create my own controller to override Devise's default registrations controller as explained in the second answer of this stack overflow article:
Override devise registrations controller
So now I am getting into that controller which currently looks like this:
class Users::RegistrationsController < Devise::RegistrationsController
def edit
super
end
end
However, when I get to my view, it's still SKIPPING the block that starts like this:
<%= f.simple_fields_for :addresses do |a| %>
However, if I go manually into my DB and add a record in the addresses table and link it to my currently_signed in user via the foreign key, then the block does not get skipped. So whats the best way to generate this connection if the address record does not yet exist? Is it the build method? e.g.
user.address.build
in the controller
SOLUTION
Yes, I needed to added this method to my new registrations_controller.rb file
def edit
if resource.addresses.size == 0
resource.addresses.build
end
super
end
It is now working the way I intended it.
You need to do it like this:
#app/models/user.rb
Class User < ActiveRecord::Base
has_one :address
before_create :build_address, unless: Proc {|x| x.address.present? } #-> not sure about the if statement
end
#app/models/address.rb
Class Address < ActiveRecord::Base
belongs_to :user
end
--
Devise does come with Controllers
These controllers are not shown in your app (they are installed with the Devise gem, but only visible in production):
- confirmations_controller.rb
- omniauth_callbacks_controller.rb
- passwords_controller.rb
- registrations_controller.rb
- sessions_controller.rb
- unlocks_controller.rb
--
You Don't Need Them
Whilst you can override these controllers, you won't need to, as your edit action will be tied to the users controller:
#config/routes.rb
resources :users
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def edit
#user = User.find params[:id]
end
end
#app/views/users/edit.html.erb
<%= form_for #user do |f| %>
...
<%= f.fields_for :address do |a| %>
<% end %>
<% end %>
Update
Sorry for not explaining. You can use resources :users with devise_for :users:
#config/routes.rb
devise_for :users
resources :users, only: [:edit, :update]
If this does not work, you may need to change the path_names argument for devise, like so:
#config/routes.rb
devise_for :users, path_names: { sign_in: 'login', password: 'forgot', confirmation: 'confirm', unlock: 'unblock', sign_up: 'register', sign_out: 'logout'}
-
Form
The form can have resource, but I think you need to work out what you're trying to change. If you're trying to change a devise object, then use the resource helper; but if you're trying to change the User model directly - I'd be partial to changing that!
The issue I think you have is if you're using resource, it's going to route to Devise controller actions. I would try setting #user in the edit action of your users controller, and use the conventional way to update
it looks like you didn't made the relation between those models add to addresses column called user_id and add to user model:
has_many :addresses
and into addresses:
belongs_to :users
then in your view you can build this form using nested attributes see this post:
Rails 4 Nested Attributes Unpermitted Parameters
another option that you can do is to show that addresses form after the user already signed in then when he update the form find the user_id with current_user and build the record using this id but using strong params in Rails 4 is recommended to solve your issue.
So I have an email field in a model.
I would like a path in this model, where a field shows up in the view, and when I type an email address, and that address matches an existing model, it gets deleted. For unsubscribing from newsletter.
something like this:
newsletter_controller.rb
def unsubscribe(email)
#newsletter = Newsletter.where(:email => email)
#newsletter.destroy
end
in the view:
simple_form_for #newsletter do |f|
f.input :email, method: delete
end
I got no idea how the view should work in the Rails Way.
In config/routes.rb, I suppose you have resources :newsletters
Add a route for unsubscribe as following:
resources :newsletters do
post 'unsubscribe', :on => :member
end
Check rake routes, you should have obtained a route path as unsubscribe_newsletter_path with POST verb.
Now, in your view:
=form_for(:newsletter, :url => unsubscribe_newsletter_path) do |f|
=f.label :email
=f.text_field :email
=f.submit "Unsubscribe"
(Change it as per syntax of simple_form)
Now, in newsletter_controller.rb(it should have been newsletter*s*_controller), add the method as:
def unsubscribe
#newsletter = Newsletter.where(:email => params[:newsletter][:email])
#newsletter.destroy if #newsletter
redirect_to root_path
end
I hope it helps. Comment with places where it gives you error or doesn't work. I'll try to help.
Good luck. :)