I am new to Rails. Particularly in dealing with the vagaries between Rails 3 and 4. I have been learning from RailsCast and MHartl's tutorial.
I successfully got the code in RailsCast #274 to work by using the answer in the question linked below:
ActiveModel::ForbiddenAttributesError in PasswordResetsController#update
My concern is that this fix will leave me vulnerable to issues in the future, be it security or otherwise. If there is a "right" way to do this I would like to know. Here is my code block:
class PasswordResetsController < ApplicationController
def create
user = User.find_by_email(params[:email])
user.send_password_reset if user
redirect_to root_url, :notice => "Email sent with password reset instructions."
end
def edit
#user = User.find_by_password_reset_token!(params[:id])
end
def update
#user = User.find_by_password_reset_token!(params[:id])
if #user.password_reset_sent_at < 2.hours.ago
redirect_to new_password_reset_path, :alert => "Password reset has expired."
elsif #user.update_attributes(params.require(:user).permit(:password, :password_confirmation))
redirect_to root_url, :notice => "Password has been reset."
else
render :edit
end
end
end
you need to setup your params first. define a private method inside your class
private
def model_params
params.require(:model).permit(:list :all :your :attributes)
end
then when you do an update, use something like:
#model.update(model_params)
mass assignment is a cool thing in rails, but you need to make sure you are protected
hope that helps
Related
What does redirecting to a particular instance mean? I am aware of how the redirecting works.
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
I understand the following ways of redirecting:
redirect_to :new (redirect to new method and displaying the new.html.erb file)
redirect_to "show" (redirect to show.html.erb file or the path for show method)
but what does redirect_to #user mean? Which method and path are we are redirecting to?
If you check the redirect_to documentation, you will find this.
Record - The URL will be generated by calling url_for with the options, which will reference a named URL for that record.
It's Rails "magic" for redirecting to the #show action for that #user using GET. You'll find similar things in default Rails forms as well, but for actions like POST.
According to section 7.4.1 from Michael Hartl's The Rails Tutorial:
redirect_to #user
can be written instead of
redirect_to user_url(#user)
Quoting Michael Hartl:
This is because Rails automatically infers from redirect_to #user that
we want to redirect to user_url(#user).
I am learning to refactor my code but I am having trouble refactoring a sessions_controller I have in my application.It is violating the "tell don't ask" principle.I am thinking of extracting some logic to its own class but not sure how that would work.Here is the code from the controller.
class SessionsController < ApplicationController
def create
admin = Admin.find_by(email: params[:sessions][:email])
if admin && admin.authenticate(params[:sessions][:password])
sign_in admin
redirect_to anasayfa_path
flash[:success] = 'Başarılı şekilde giriş yapıldı'
else
redirect_to root_path
flash[:error] = 'Giriş bilgilerinde bir hata var'
end
end
end
How would I refactor this?I thought of extracting
admin && admin.authenticate(params[:sessions][:password])
from this method but would that be the best way?Where would I put the class if I extracted this?
First, you could extract admin lookup, as it may be reused in other actions.
Also, you could standardize your flash keys : notice and alert are two standard keys that #redirect_to understands.
class SessionsController < ApplicationController
before_filter :find_admin
def create
if #admin.authenticate(params[:sessions][:password])
sign_in #admin
redirect_to anasayfa_path, notice: 'Başarılı şekilde giriş yapıldı'
else
redirect_to root_path, alert: 'Giriş bilgilerinde bir hata var'
end
end
private
def find_admin
#admin = Admin.where(email: params[:sessions][:email]).first or redirect_to( root_path, alert: 'not logged in' )
end
end
You have to use #where instead of #find_by to avoid exception if admin is not found.
If you want to keep your current flash keys, you can add in an initializer :
ActionController::Flash.add_flash_types( :success, :error )
Path in #redirect_to from #find_admin and when authentication fail should probably points to log in url.
Try this
class SessionsController < ApplicationController
def create
admin = Admin.find_by(email: params[:sessions][:email])
login_status = false
if admin && admin.authenticate(params[:sessions][:password])
sign_in admin
login_status = true
end
login_status ? redirect_to(anasayfa_path, :flash => {:success => 'Başarılı şekilde giriş yapıldı'}) : redirect_to(root_path, :flash => {:error => 'Giriş bilgilerinde bir hata var'})
end
end
I am trying to refactor the update action in my Rails action, so that users can change their own email address only after confirming it by clicking on a link that I send to them.
class UsersController < ApplicationController
before_filter :authorized_user
def update
current_email = #user.email
new_email = params[:user][:email].downcase.to_s
if #user.update_attributes(params[:user])
if new_email != current_email
#user.change_email(current_email, new_email)
flash[:success] = "Please click on the link that we've sent you."
else
flash[:success] = "User updated."
end
redirect_to edit_user_path(#user)
else
render :edit
end
end
def confirm_email
#user = User.find_by_email_token!(params[:id])
#user.email = #user.new_email
#user.save
end
private
def authorized_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
end
This function saves the new email to a database field new_email. email will be replaced only after the user has confirmed his new_email through a URL:
class User < ActiveRecord::Base
def change_email(old_email, new_email)
self.email = old_email
self.new_email = new_email.downcase
self.send_email_confirmation_link
end
end
The code partially works, but I wonder if there's a more elegant way to do this, maybe by using an after_save callback or at least moving more code to the model.
What would be the best way to do this?
Thanks for any help!
P.S. Please don't suggest to use Devise for this. I really want to build my own authentication system here :-)
I would advise you against using ActiveRecord callbacks to perform business logic: ActiveRecord models should only be a thin wrapper around the database persistence layer.
Look at how the controller code can be changed:
def update
if UpdatesUserCheckingEmail.new(#user, params[:user], flash).execute!
redirect_to edit_user_path(#user)
else
render :edit
end
end
All the business logic is performed by an external object, which encapsulates all your business logic (which you can put in app/services/updates_user_checking_email.rb)
class UpdatesUserCheckingEmail
attr_reader :user, :user_params, :flash
def initialize(user, user_params, options = {})
#user = user
#user_params = user_params
#flash = options[:flash]
end
def execute!
if user.update_attributes(user_params)
if new_email != current_email
user.change_email(current_email, new_email)
flash[:success] = "Please click on the link that we've sent you."
else
flash[:success] = "User updated."
end
end
end
private
def current_email
user.email
end
def new_email
user_params[:email].downcase.to_s
end
end
I'd also advise you to move the logic which sends the email out of the ActiveRecord model and inside a dedicated service object. This will make your app much more easier to change (and to test) in the future!
You can find a lot more about these concepts here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
I think you should not check current_email and new_email after update in database because it should before the database update. Another one is you are sending link to user after updating email to the database. So, that could not meet your goal i.e. "email will be replaced only after the user has confirmed his new_email through a URL." You should create new action for updating user email or you should write logic for updating user email when user get email of "reset email" in update action of UserController. Following is something simple approach to resolve your problem:
class UsersController < ApplicationController
def send_email_rest
#user.change_email(#user.email, params[:new_email]) if params[:new_email].present?
end
def update
if #user.update_attributes(params[:user])
#stuff you want to do
end
end
end
Hope that helps!!!
Okay I have been working on this for about 2 hours and am sure there is a very simple solution but unfortunately as a rails novice I can't seem to find it.
I have set up a basic sign up and login system following Ryan Bates Authetication lesson #270.
I keep however receiving the same error when trying to login.
undefined method `find_by_email' for nil:NilClass
I have my sessions controller set up as follows:
class SessionsController < ApplicationController
def new
end
def create
user = user.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Logged Out!"
end
end
But am really unsure how to define find_by_email.
Any advice people can offer me on this really would be much appreciated
user.find_by_email(params[:email])
should be
User.find_by_email(params[:email])
I am working on a Rails App that uses Devise as the authentication module, however I want to customize it so that CanCan will only permit Administrators to create a new user. I am having a hard time understanding how to customize the controller for Devise so that this can be done. Any help would be appreciated.
You don't need to customize anything :D
Remove :registerable from your Devise model.
Create your Users CRUD* (just scaffold users)
Use CanCan for user permission on your Users
Controller.
*Check Devise's wiki on how to create a Users CRUD, there is a routing trick you need to do
You can create a "User" controller that will manage users and then simply set permissions for it. So in your new User controller you can have something like:
class UserController < ApplicationController
load_and_authorize_resource
def index
end
def new
end
def show
end
def create
if #user.save
flash[:notice] = "Successfully created User."
redirect_to root_path
else
render :action => 'new'
end
end
def edit
end
def update
params[:user].delete(:password) if params[:user][:password].blank?
params[:user].delete(:password_confirmation) if params[:user][:password].blank? and params[:user][:password_confirmation].blank?
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully updated User."
redirect_to user_index_path
else
render :action => 'edit'
end
end
def destroy
if #user.destroy
flash[:notice] = "Successfully deleted User."
redirect_to user_index_path
end
end
end
Assuming you have administrators set to:
can :manage, :all
You should be good to go.
In your routes file you'll need to set up your routes:
resources :user, :controller => "user"
Hope this helps!