1
votes

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'?

2
did you ever solve this?Timmy Von Heiss

2 Answers

2
votes

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.

0
votes

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.