Using a Model method to populate a column during a migration - ruby-on-rails

I'd like to add a new column to my users table, and populate it with a random token. I've got that working, but I'm curious as to why the first method I tried didn't work.
Here's the working version of my migration:
class AddTokenToUser < ActiveRecord::Migration
def up
add_column :users, :secure_token, :string
User.reset_column_information
User.all.each do |user|
user.secure_token = SecureRandom.urlsafe_base64(16)
user.save!
end
end
def down
remove_column :users, :secure_token
end
end
However, because I'm also going to want the code to generate this token on the User model, as I'd like to create a new token along with every new user, I thought I might be able to add the code as a method on the User object:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :snaps
def generate_new_secure_token
#secure_token = SecureRandom.urlsafe_base64(16)
end
end
...and then call it from the migration, to avoid repeating myself:
class AddTokenToUser < ActiveRecord::Migration
def up
add_column :users, :secure_token, :string
User.reset_column_information
User.all.each do |user|
# user.secure_token = SecureRandom.urlsafe_base64(16)
user.generate_new_secure_token
user.save!
end
end
def down
remove_column :users, :secure_token
end
end
However, with this method, I get no errors, but my secure_token column values all end up as NULL in the database, rather than having the a token in them.
I'm new to both Rails and Ruby, so I figure I'm missing something obvious, but I can't see what it is. Why isn't my method working, and is there a good way to move the token generation routine to the User class, so I don't need to have it in two different places?

Change your method to this
def generate_new_secure_token
self.secure_token = SecureRandom.urlsafe_base64(16)
end
#secure_token is an instance variable. Setting that doesn't change the attribute secure_token on the user object

Related

devise confirmable on rails 5 not working

I am on a rails 5 app. Using devise for user management. I have 'confirmable' enabled in my user model and the migrations are also up for the same. I have created a custom mailer class which is derived from Devise::Mailer, have setup the mailer in devise config to use the custom mailer class however my confirmations email are not being sent properly.
Have tried many so answer threads but couldn't crack it yet. Where could I be going wrong?
User Model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
after_create :send_confirmation_email
def send_confirmation_email
p 'inside after_create callback func.'
MyDeviseMailer.confirmation_instructions(self).deliver
end
end
Migration for confirmable
def up
add_column :users, :confirmation_token, :string
add_column :users, :unconfirmed_email, :string
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_sent_at, :datetime
add_index :users, :confirmation_token, unique: true
end
My custom mailer class derived from Devise::Mailer
class MyDeviseMailer < Devise::Mailer
helper :application
include Devise::Controllers::UrlHelpers
default template_path: 'devise/mailer'
def confirmation_instructions(record, token, opts={})
devise_mail(record, :confirmation_instructions, opts)
end
end
After a couple of hours, figured out that i was following the wrong way of doing things.Followed the well written article over here.Have updated my solution below:
Refactored confirmation_instructions method present in my custom mailer class as shown below:
def confirmation_instructions(record, token, opts={})
if record.email.present?
opts[:subject] = "Welcome #{record.email.split('#').first.capitalize}, Confirm your asana account"
else
opts[:subject] = "Confirm Your Asana Account"
end
super
end
Changed the function invocation to this in my user model:
MyDeviseMailer.confirmation_instructions(self, self.confirmation_token).deliver
This is what worked for me finally.

Couldn't find User without an ID - Ruby 2.1.4 / Rails 4.1.6

I am facing an error while trying to link the :username in my User table and my Room table. I made a custom auth with devise and added :username.
I would like the username to be the link between the User table from devise and my Room table.
I am trying to build this app to recreate a kind of airbnb but mainly as an exercise as I started programming in ruby few months ago.
I get the error:
ActiveRecord::RecordNotFound in RoomsController#new
Couldn't find User without an ID
line #19 #room.username = User.find(params[:username])
Thank you very much for your help. I am stuck in here for hours now :-(
rooms_controller
def new
#room = Room.new
#room.username = User.find(params[:username]) #error seems to come from here
end
routes.rb
Rails.application.routes.draw do
devise_for :users
get "home/info"
root :to => "home#info"
resources :rooms
resources :users do
resources :rooms
end
room.rb
class Room < ActiveRecord::Base
mount_uploader :photo, PictureUploader
belongs_to :user
validates_presence_of :username, :location, :description, :capacity, :price_day, :photo
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates_presence_of :username
validates_uniqueness_of :username
has_many :rooms
end
It should be something like this
def new
#room = Room.new
#room.username = User.find_by_username(params[:username])
end
If you just use .find() it expects the id of the user. Also see http://guides.rubyonrails.org/active_record_querying.html#dynamic-finders
There is a logic error in that you are saving #room.username to the User Object. You should be setting #room.user = User.find_by(...) OR #room.user_id = User.find_by(...).id
Active record will automagically make a method for you that will be #room.user.username if you want to get the username.
Now, here are the ways to find a user.
#room = Room.new #Then either of the following
#room.user = User.find_by(username: params[:username]) #Returns only one value
#room.user = User.find_by_username(params[:username]) #Returns only one value
#room.user = User.where(username: params[:username]) #Returns all users which meet condition.
As already mentioned in the answers, User.find() takes an ID. One thing to know is that all methods that start with .find for active record return a single record even if many meet the condition.
If you are having any problems still, then show us your Database Schema, and we can help further.
I found a solution. The Room is created with the the right :username and nothing is seen by the user.
In my Rooms controller, I kept
def new
#room = Room.new end
And I added this line in the "def create" part :
def create
#room = Room.new(room_params)
#room.username = current_user.username
Thank you for your help, this help me to understand better the relations in rails.
Have a nice day !

Rails and devise: model attribute not being persisted

I edited devise's RegistrationsController::create method to modify slightly the behaviour. What I'm trying to do is that if there are no admin users in the database, the one that first signs up is going to be assigned the admin role, else it will be a regular user.
However, the role, though assigned correctly to the object (tested), it's not being persisted to the database.
Model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessor :role
Roles = [ :admin, :default ]
def is? requested_role
self.role == requested_role.to_s
end
def self.admin_role
return Roles[0]
end
def self.default_role
return Roles[1]
end
end
Modified devise method:
def create
build_resource(sign_up_params)
admin_user = User.find_by_role(User.admin_role)
if admin_user.nil?
resource.role = User.admin_role
else
resource.role = User.default_role
end
# here puts resource.role shows what's expected is indeed being assigned to the object
if resource.save
...
end
end
Why isn't the role being stored in the database? Why is it NULL?
You don't need the attr_accessor for :role if you have this defined as a column on your table. ActiveRecord gives you the database backed accessors just by having the relevant column defined in the relevant table.
Your attr_accessor will be overriding these and preventing them from persisting your changes to the database.

How to associate a Devise User with another existing model?

I am very new to Ruby on Rails and have setup Devise for authentication. I have an existing model that I created prior to adding Devise. That model is called Article. I believe I have done everything I need to do in order to use the association=(associate) method that "assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associate object and setting this object’s foreign key to the same value" which is exactly what I need to do.
Here is Devise's User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
has_one :article
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Here is my Article model:
class Article < ActiveRecord::Base
belongs_to :user
validates :name, presence: true, length: { minimum: 5 }
end
Here is my migration:
class AddUserRefToArticles < ActiveRecord::Migration
def change
add_reference :articles, :user, index: true
end
end
Here is my create method from my articles_controller.rb:
def create
#article.user = current_user
#article = Article.new(post_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
And here is what happens when my controller runs:
NoMethodError in ArticlesController#create
undefined method `user=' for nil:NilClass
The highlighted code is #article.user = current_user. I was at least glad to know that I wrote that line of code similar to the popular answer in the Devise how to associate current user to post? question that I saw on here before posting this one.
I know I'm making a rookie mistake. What is it?
A new User instance needs to be assigned to #article before you can access any of the instance's attributes/associations. Try the following:
#article = Article.new(post_params) # Assign first
#article.user = current_user # Then access attributes/associations
The code posted in the question yields a nil:NilClassexception because the user association is being invoked on #article, which is nil because nothing has yet been assigned to it.

Anonymous user in devise - rails

I'm new to rails and I tried to make simple authentication with anonymous user. I followed this tutorial and I have this error:
undefined method `find_or_initialize_by_token'
This is my AnonymousUser model:
class AnonymousUser < User
ACCESSIBLE_ATTRS = [:name, :email]
attr_accessible *ACCESSIBLE_ATTRS, :type, :token, as: :registrant
def register(params)
params = params.merge(type: 'User', token: nil)
self.update_attributes(params, as: :registrant)
end
end
This is my User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :confirmable, :lockable, :recoverable,
:rememberable, :registerable, :trackable, :timeoutable, :validatable,
:token_authenticatable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
And the last one important is my ApplicationController which has this error:
class ApplicationController < ActionController::Base
protect_from_forgery
def authenticate_user!(*args)
current_user.present? || super(*args)
end
def current_user
super || AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
end
private
def anonymous_user_token
session[:user_token] ||= SecureRandom.hex(8)
end
end
Someone told me that if AnonymousUser user inherits from User then AnonymousUser have method called find_or_initialize_by_token, but i don't know how to fix it.
Provided you have latest rails installed, try to refactor:
# in ApplicationController#current_user
AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
to something like this:
AnonymousUser.safely_find(anonymous_user_token)
and push the find_or_initialize_by_token and save(validate: false) into the model.
I wrote the blog post you referenced, but today, I would use
AnonymousUser.where(anonymous_user_token: anonymous_user_token).first_or_initialize
Dynamic finders have been deprecated AFAIK.
However, #Saurabh Jain is absolutely correct in his suggestion to refactor that block into a nice little push-button class method on the AnonymousUser.

Resources