hartl tutorial chapter 10 - unable to find ID

I'm struggling with one issue of the Hartl tutorial (where I've added my own custom needs, which is that a user belongs to a company, and the creation form is a company#new.) now everything is working, such as password reset etc. Except one thing which is the activation_token method.

Now I've tried to debug to get down to the exact line which is the problem and it is this one:

 @user.activation_token

when I try printing that out just generally for a logged in user it's blank. Now the accurate error I get is:

No route matches {:action=>"edit", :controller=>"account_activations", :email=>"[email protected]", :id=>nil} missing required keys: [:id]

the request parameters are as follows:

"company"=>{"users_attributes"=>{"0"=>{"first_name"=>"demo", "last_name"=>"demo", "email"=>"[email protected]", "password"=>"demodemo"}}, "name"=>"demo"}

and the errors happen on:

<%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>

my User.rb:

class User < ActiveRecord::Base
  attr_accessor :remember_token, :activation_token, :reset_token
  before_create :create_activation_digest
  ...

 class << self
    # Returns the hash digest of the given string.
    def digest(string)
      cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                    BCrypt::Engine.cost
      BCrypt::Password.create(string, cost: cost)
    end

    # Returns a random token
    def new_token
      SecureRandom.urlsafe_base64
    end
 end

private

    # Creates and assigns the activation token and digest.
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end

now it works in obtaining the ID when I have it in my test and do the following:

  def account_activation
    user = User.first
    user.activation_token = User.new_token
    UserMailer.account_activation(user)
  end

and my companycontroller#create looks like this:

  def create
    @company = Company.new(company_params)
    if @company.save
      user = @company.users.first
      user.send_activation_email
      flash[:info] = "registerd and email sent"
      redirect_to root_url
    else
      flash.now[:danger] = 'issues with creating user.'
      render 'new'
    end
  end

my company params:

  def company_params
    params.require(:company).permit(:name, users_attributes: [:id, :first_name, :last_name, :email, :password])
  end

routes:

Rails.application.routes.draw do
  get 'password_resets/new'

  get 'password_resets/edit'

  root    'welcome#index'
  get     'company/users' => 'companies#users'
  get     'login'         => 'sessions#new'
  post    'login'         => 'sessions#create'
  delete  'logout'        => 'sessions#destroy'
  resources :companies
  resources :users
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
end

note: the user does successfully save, as does the company. I'm also able to obtain general user info, when I uncomment .activation_token I can see that I'm able to get the @user.first_name etc in the email from my console.

on the picture you can see @user.activation_token is nil, but it finds the user object

on the picture you can see that the @user.activation_token is nil, but it finds the @user object.

Upvotes: 1

Views: 118

Answers (1)

Richard Peck
Richard Peck

Reputation: 76774

The problem isn't your activation_token method, as that seems to be created and called - according to your screenshot.

The problem is you're populating the edit_activations_path with @user.activation_token (which won't include id), when that path requires id.

You should change it to:

<%= link_to "Activate", edit_account_activation_url @user %>

According to your routes (which I've improved below), and the Rails standard practice of passing objects to route actions means that you should pass the entire object, and let Rails determine the specifics.

Your error states that it's id that's not being passed - showing us that if you passed the @user object, instead of @user.activation_token, it will give Rails the ability to use it.

The only caveat here is that you may be calling activation_token and email in your Activations controller. If this is the case, you need to rework your routes to accept the token and email respectively (below):


There are a number of improvements you could make to your code:

--

Your routes can be made much more efficient:

 #config/routes.rb
  root    'welcome#index'

  resource  :company, only: [] do
     get :users, on: :collection
  end
  resources :companies, :users
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :sessions,            only: [:new, :create, :destroy], path_names: { new: "login", create: "login", destroy: "logout" }

Now, the important thing to note here is the account_activations resource is going to be the standard Rails resource... IE will expect id to formulate the route. Typical ID will be the primary_key of the object, you want to use the activation_token. To fix this, you should use the following:

<%= link_to "Activate", edit_account_activation_url(id: @user.activation_token, email: @user.email) %>

This will still accept the params as :id (required) and :email, but will be populated with the data you need, allowing you to use:

#app/controllers/activations_controller.rb
class ActivationsController < ApplicationController
   def edit
     token = params[:id]
     email = params[:email]
   end
end

--

The model invokes User twice. You should call the following:

def create_activation_digest
  activation_token  = class.new_token
  activation_digest = class.digest(activation_token)
end

Upvotes: 1

Related Questions