Reputation: 847
I'm unsure if the way I'm treating it is the issue, or the ERB.
Right now when a user registers, I sent an activation email. If they haven't activated, and try to log back in, they're prompted "Sorry, you're not authorized." I want to modify it to so it offers them to be resent the email, as well.
SessionsController
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
message = "Account not activated. "
message += "Check your email for the activation link, or click" + <%= link_to "here", :controller => :user, :action => :resend_email %>+ "to have it resent!"
flash[:warning] = message
redirect_to root_url
end
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
Within the Users controller I made a 'resend_email' function. Essentially just majority of the create one, so a bit redundant.
UsersController
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
@users = User.where(activated: true).paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
redirect_to root_url and return unless @user.activated?
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
@user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
def resend_email
@user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
I've tried modifying the 'message' to several different versions and each time I get a response similar to
/home/ubuntu/workspace/sample_app/app/controllers/sessions_controller.rb:16: syntax error, unexpected '<' <%= link_to "here", :controlle... ^ /home/ubuntu/workspace/sample_app/app/controllers/sessions_controller.rb:16: syntax error, unexpected ',', expecting keyword_end ...o "here", :controller => :user, :action => :resend_email %> ... ^ /home/ubuntu/workspace/sample_app/app/controllers/sessions_controller.rb:16: syntax error, unexpected '>' ...r, :action => :resend_email %> ... ^
I got this syntax from previous experience, and people with similar needs to mine.
So I'm wondering what's the best way to do this, without a gem (I did find Devise functionality, but I'm trying to learn how to do it on my own)
I also read that sometimes ERB and plain text don't work well. However, I also get an error with message is only <%= link_to "here", :controller => :user, :action => :resend_email %>
as well.
I'm unsure if any other controllers are needed.
EDIT INFO
Leaving original copy for other people with the issue. After reading what max provided in his answer below, I have modified a few things.
Changes to SessionsController
link = view_context.instance_exec do
ERB.new("<%= link_to 'here', :controller => :users, :action => :resend_activation %>").result(binding)
end
message = "Account not activated. "
message += "Check your email for the activation."
message += link # This is for demo purposes, just needed an output
User Controller
def resend_activation @user = User.find(params[:email]) @user.send_activation_email flash[:info] = "Please check your email to activate your account." redirect_to root_url end
Partial View for Warnings
<% flash.each do |message_type, message| %>
<%= content_tag(:div, sanitize(message), class: "alert alert-#{message_type}") %>
<% end %>
Up to this point I am now seeing the link I expected, and my issue is when I click it.
Couldn't find User with 'id'=
- I've tried different uses of User
and even told it re-search based on the email params.
So I tried updating my routes to
Routes
Rails.application.routes.draw do
root 'static_pages#home'
get '/home', to: 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
post '/signup', to: 'users#create'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
post '/resend_activation:email' => 'account_activations#resend_activation',
:constraints => { :email => /[^\/]+/ }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Which made progress (I think), and it now prompts me "no get resource found". I'm doing research on when to use get vs post, and I thought I went the right method. But why is it trying to reference that path if right after it sends the email, it's supposed to go back to root_url?
Thanks again.
Update # 2:
I was able to get the errors to stop by adding in route switches, and modifying my sessions_controller to
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
message = "Account not activated. "
message += "Check your email for the activation."
message += " #{view_context.link_to "Resend Activation E-Mail", { action: "resend_activation",
controller: "account_activations", email: user.email }, method: :post}"
flash[:warning] = message
redirect_to root_url
end
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
But now no emails are going out, everything just routes back to login.
heroku logs --tail
Tells me
Heroku Logs
2016-08-24T09:44:48.990703+00:00 app[web.1]: I, [2016-08-24T09:44:48.990609 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Started GET "/resend_activation/[email protected]" for 100.15.65.126 at 2016-08-24 09:44:48 +0000
2016-08-24T09:44:48.992317+00:00 app[web.1]: I, [2016-08-24T09:44:48.992217 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Processing by StaticPagesController#home as
2016-08-24T09:44:48.992394+00:00 app[web.1]: I, [2016-08-24T09:44:48.992349 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Parameters: {"email"=>"[email protected]"}
2016-08-24T09:44:48.997712+00:00 app[web.1]: I, [2016-08-24T09:44:48.997648 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Rendering static_pages/home.html.erb within layouts/application
2016-08-24T09:44:48.999032+00:00 app[web.1]: I, [2016-08-24T09:44:48.998965 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Rendered static_pages/home.html.erb within layouts/application (1.1ms)
2016-08-24T09:44:49.010260+00:00 app[web.1]: I, [2016-08-24T09:44:49.010186 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Rendered layouts/_shim.html.erb (0.4ms)
2016-08-24T09:44:49.010516+00:00 app[web.1]: I, [2016-08-24T09:44:49.010461 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Rendered layouts/_shim.html.erb (0.0ms)
2016-08-24T09:44:49.010642+00:00 app[web.1]: I, [2016-08-24T09:44:49.010591 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Rendered layouts/_headElement.html.erb (7.6ms)
2016-08-24T09:44:49.020206+00:00 app[web.1]: D, [2016-08-24T09:44:49.020136 #5] DEBUG -- : [8cfcee3c-133c-489e-8877-523578821d67] User Load (1.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 103], ["LIMIT", 1]]
2016-08-24T09:44:49.020630+00:00 app[web.1]: I, [2016-08-24T09:44:49.020565 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Rendered layouts/_header.html.erb (3.8ms)
2016-08-24T09:44:49.025024+00:00 app[web.1]: I, [2016-08-24T09:44:49.024957 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Rendered layouts/_footer.html.erb (0.7ms)
2016-08-24T09:44:49.025337+00:00 app[web.1]: I, [2016-08-24T09:44:49.025273 #5] INFO -- : [8cfcee3c-133c-489e-8877-523578821d67] Completed 200 OK in 33ms (Views: 26.1ms | ActiveRecord: 1.8ms)
It finds the member, and renders it, but is no longer mailing. I think it's because of the get route I had to establish to make it 'work'?
Upvotes: 1
Views: 1207
Reputation: 2218
The reason why your route is telling you that id = nil
is because your :activation_token
is the id
in that route and it is not stored in the database, it is created virtually with attr_accessor
. Instead, store :activation_token
as a column in your database.
Upvotes: 0
Reputation: 26778
With the following code:
message +=
"..."" +
<%= link_to "here", :controller => :user, :action => :resend_email %> +
"..."
You're trying to use ERB tags in regular ruby - that won't work. You can only use ERB in templates.
Normally I'd advise using Ruby's standard #{}
string interpolation but that won't actually solve the problem here.
link_to
is only available to views by default, although you can get access to it via the view_context
object:
link = "#{view_context.link_to 'here', :controller => :user, :action => :resend_email}"
By the way, it possible to use ERB in controllers if you compile it yourself:
link = ERB.new("<%= view_context.link_to(...) %>").result(binding)
You can change the variables/methods available to ERB by invoking it in a different context, i.e.:
link = view_context.instance_exec do
ERB.new("<%= link_to(...) %>").result(binding)
end
This works with the standard #{}
string interpolation too:
link = view_context.instance_exec do
"#{link_to(...)}"
end
It's worth mentioning that if you make a custom html string in the controller (like you are doing here with flash), when you display the text on the view you will need to add some custom methods to make the html display as real html:
# in controller
flash[:test] = "<span>some html</span>"
# in view
<%= raw flash[:test].html_safe %>
This way only the text some html
would be displayed, and not the entire string <span>some html</span>
There's a reason raw
and html_safe
is necessary, and that's because printing html has security risks and Rails is designed to make it more difficult.
Say your user sets their username as "<script>alert("hacked")</script>"
and this string somehow makes it on to the page as true html. You would have just exposed your users to XSS (Cross Site Scripting), which you don't want to do. So make sure that when you use raw <string>.html_safe
you are not displaying anything user-generated.
Upvotes: 2