Add a random string while updating attribute - ruby-on-rails

I am looking for a method that can generate a random string in the starting of the email field while updating the record.
def update
#user = User.find_by_id(4)
#user.email = #method to update email with random string
end
So if I have the email record abc#gmail.com and I want to update it like this:
dssaakj123_abc#gmail.com
How it can be done in rails?

You can use the SecureRandom library:
#user.email = "#{SecureRandom.hex(10)}_#{user.email}"

Why not use SecureRandom?
require 'securerandom'
random_string = SecureRandom.hex # provide argument to limit the no. of characters
# outputs: 5b5cd0da3121fc53b4bc84d0c8af2e81 (i.e. 32 chars of 0..9, a..f)
For appending before email, you can do something like
#user.email = "#{SecureRandom.hex(5))_#{#user.email}" # 5 is no. of characters
Hope it helps!

(1..8).map{|i| ('a'..'z').to_a[rand(26)]}.join
8 is the number of characters you want to generate randomly.

create an action in your application controller like this :
private
def generate_random_string
SecureRandom.urlsafe_base64(nil, false)
end
And use it like this in any controller you want:
def update
#user = User.find_by_id(4)
#user.email = generate_random_string + #user.email
end

I hope this will help you.
def update
#user = User.find_by_id(4)
#user.email = "#{generate_random_string(8)}_#{#user.email}"
## You can pass any length to generate_random_string method, in this I have passed 8.
end
private
def generate_random_string(length)
options = { :length => length.to_i, :chars => ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a }
Array.new(options[:length]) { options[:chars].to_a[rand(options[:chars].to_a.size)] }.join
end

Related

How do I perform an ActiveRecord query after form submission?

Would this work? I want to do something like coins transfer
#logs = Logs.new(log_params)
#logs.save
#tt = Users.where(email: params[:email]).update(money: Users.find(current_user.id)['money'] - params[:money])
#tt.save
#wr = Users.find(current_user.id).update(money: Users.where(email: params[:email])['money'] + params[:money])
#wr.save
I don't know if you are planning to display those values in your views, but you could do something like this:
sender = Users.where(email: params[:email]).first
# This returns the user
recipient = Users.find(current_user.id)
# or just current_user if it inherit from the User class
money_to_substract = recipient.money - params[:money]
money_to_sum = recipient.money + params[:money]
Then your transsaction would be a bit dryier
User.transaction do
#logs = Logs.new(log_params)
#tt = sender.update(money: money_to_substract)
# update saves to the database so no need to call save
# #tt.save
#wr = recipient.update(money: money_to_sum)
# update saves to the database so no need to call save
# #wr.save
end
But IMHO, I would do something like this:
models/User.rb
class User < ActiveRecord::Base
...
def sends_money(amount)
money_to_substract = self.money - amount
update(money: money_to_substract)
end
def receives_money(amount)
money_to_sum = self.money + amount
update(money: money_to_sum)
end
...
end
In your controller
amount = params[:money].to_i
User.transaction do
#logs = Logs.new(log_params)
#tt = sender.sends_money(amount)
#wr = recipient.receives_money(amount)
end
Which makes things easier to read and follow through your code.
Hope this helps!

Stack level too deep on user.save

I want to assign a confirmation code to my users while creating one. And I also titleize some columns before saving-updating them. So my user.rb looks like this (it may be a bit messy):
// user.rb
*** some code ***
before_save { titleize_column(:name)
titleize_column(:surname)
capitalize_column(:complaints)
capitalize_column(:education)
capitalize_column(:job)
capitalize_column(:complaintsdetails)
capitalize_column(:prediagnosis)
capitalize_column(:existingdiagnosis)
capitalize_column(:knownilnessesother)
capitalize_column(:usedmedicine)
capitalize_column(:operation)
capitalize_column(:trauma)
capitalize_column(:allergy)
capitalize_column(:otherhabits)
capitalize_column(:motherother)
capitalize_column(:fatherother)
capitalize_column(:siblingsother)
}
before_save :generate_confirmation_code
protected
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
update_attribute :confirmation_code, SecureRandom.urlsafe_base64(20)
update_attribute :confirmed, false
else
update_attribute :confirmed, true
end
end
end
protected
def capitalize_column(attr)
unless self[attr].nil?
self[attr] = Unicode::capitalize self[attr]
end
end
protected
def titleize_column(attr)
unless self[attr].nil?
words = self[attr].split
words.each_with_index do |v,i|
words[i] = Unicode::capitalize v
end
self[attr] = words.join(" ")
end
end
I'm using separate methods for titleizing and capitalizing columns because they may be nil when first creating a user, so I'm checking if it is null or not in those methods. This structure works fine on a normal signup with strong parameters. However, if I try to use twitter signup with the method below, it gives me the error 'stack level too deep' and I can see that it calls the generate_confirmation_code 123 times from the application trace and then these happens:
app/models/user.rb:83:in each'
app/models/user.rb:83:ineach_with_index'
app/models/user.rb:83:in titleize_column'
app/models/user.rb:20:inblock in '
app/models/user.rb:64:in generate_confirmation_code' (x123 times)
app/models/user.rb:101:infrom_omniauth'
app/controllers/socials_controller.rb:4:in `create'
// method for signing up/logging in a user from twitter
class << self
def from_omniauth(auth_hash)
if exists?(uid: auth_hash['uid'])
user = find_by(uid: auth_hash['uid'])
else
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'], type: 'Patient')
user.password_digest = User.digest('111111')
user.name = auth_hash['info']['name']
user.location = get_social_location_for user.provider, auth_hash['info']['location']
user.avatar = auth_hash['info']['image']
user.url = get_social_url_for user.provider, auth_hash['info']['urls']
user.save! // THIS IS THE LINE 101!
conversation = Conversation.create()
user.conversation = conversation
admin = Admin.first
admin.conversations << conversation
user.progress = Progress.create(active_state:1)
end
user
end
I think I'm messing up by using before_save not properly, but do not know how to do it right. What am I doing wrong here?
update_attribute also fires the save callbacks, thereby looping the before_save infinitely, thus producing stack level too deep.
You can just simply assign values in a before_save callback methods, because they will simply be saved afterwards anyway. See the following:
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
self.confirmation_code = SecureRandom.urlsafe_base64(20)
self.confirmed = false
else
self.confirmed = true
end
end
end
You are calling update_attribute inside before_save callback method, instead you can just assign values to attributes. The method signature generate_confirmation_code should be like below -
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
self.confirmation_code = SecureRandom.urlsafe_base64(20)
self.confirmed = false
else
self.confirmed = true
end
end
end

How to test a specific line in a rails model using rspec

I have a model with an initializer in it, which basically creates a user from a user hash.
After it gets the user information, it checks whether the "privileges" key in the hash is an array. If it's not, it turns it into an array.
Now the obvious way of doing this would be crafting an entire user_hash so that it would skip those "create user" lines and then check if it turns the input into an array if necessary. However, I was wondering if there is a more DRY way of doing this?
Here is the user model I'm talking about:
def initialize(opts={})
#first_name = opts[:user_hash][:first]
#last_name = opts[:user_hash][:last]
#user_name = opts[:user_hash][:user_name]
#email = opts[:user_hash][:email]
#user_id = opts[:user_hash][:id]
#privileges = {}
if opts[:privs].present?
if !opts[:privs].kind_of?(Array)
opts[:privs] = [opts[:privs]]
end
end
end
You can pass a double which returns the needed value when the proper key is requested, and itself (or something else) otherwise:
it 'turns privs into an array' do
opts = double(:opts)
allow(opts)to receive(:[]).and_return(opts)
allow(opts)to receive(:[]).with(:privs).and_return('not array')
expect(MyClass.new(opts).privileges).to eq(['not array'])
end
Btw, your code could be simplified using the splat operator:
privs = [*opts[:privs]]
sample behavior:
privs = nil
[*privs]
# => []
privs = ['my', 'array']
[*privs]
# => ["my", "array"]
privs = 'my array'
[*privs]
# => ["my array"]
You can even use the idempotent Kernel#Array
def initialize(opts = {})
#first_name = opts[:user_hash][:first]
#last_name = opts[:user_hash][:last]
#user_name = opts[:user_hash][:user_name]
#email = opts[:user_hash][:email]
#user_id = opts[:user_hash][:id]
#privileges = {}
Array(opts[:privs])
end
I hope that helps
Rather than testing the implementation (value is turned into an array), I would test the desired behavior (takes single privilege or multiple privileges):
describe User do
describe '#initialize' do
it "takes single privilege" do
user = User.new(user_hash: {}, privs: 'foo')
expect(user.privileges).to eq(['foo'])
end
it "takes multiple privileges" do
user = User.new(user_hash: {}, privs: ['foo', 'bar'])
expect(user.privileges).to eq(['foo', 'bar'])
end
end
end

user mailer showing special character

as we all know that html_safe work in controller and view
i am sending some data from user_mailer.rb file and its having some special character like if i wrote &"samarth" so its show &amd&quotsamarth&quot
the code is
def alert_publication_notification(user, cust_alert, home_url)
load_mailer_settings
#content_type = "text/html"
#subject = ("[abc]- #{cust_alert.alert.name.html_safe} : mail ") due to this line i am getting special character any solution
#from = "sam"
#recipients = user.login
#bcc = Settings.emailid_alerts
#sent_on = Time.now
#customer_alert = cust_alert
#user = user
#home_url = home_url
end
use raw(some_variable) function for RAILS 3 this will not show encoded characters.

generate unique username (omniauth + devise)

I have an app with user authentication with devise + omniauth.
In my model that username in my app is unique. I dont want duplicate username in my app.
Some users in facebook has not a defined username in his profile.
I want generate an unique username if the user has not username defined in facebook.
For example for generate password I have this:
:password => Devise.friendly_token[0,20]
How can I generate a unique username for my app if the facebook user has not username in facebook?
Thank you
You can create a nice readable username (eg generated from the first part of the email) and then ensure it is unique by adding numbers until it is. eg
#in User
def get_unique_login
login_part = self.email.split("#").first
new_login = login_part.dup
num = 2
while(User.find_by_login(new_login).count > 0)
new_login = "#{login_part}#{num}"
num += 1
end
new_login
end
One problem here is that someone could potentially bag that login inbetween you getting it and you saving it. So, maybe it's best to combine it into a before_create filter:
#in User
before_create :ensure_login_uniqueness
def ensure_login_uniqueness
if self.login.blank? || User.find_by_login(self.login).count > 0
login_part = self.email.split("#").first
new_login = login_part.dup
num = 2
while(User.find_by_login(new_login).count > 0)
new_login = "#{login_part}#{num}"
num += 1
end
self.login = new_login
end
end
You can take a part of email before the # sign and add there smth like user_id, or just take the email itself. Or you can combine somehow the first and last names from the fb response.
Here is how i created Login with combination of first name and last name field.. Improvements on this code is welcome.
before_create :ensure_login_uniqueness
def ensure_login_uniqueness
if self.login.blank?
self.name = self.first_name + " " + self.last_name
firstnamePart = self.first_name.downcase.strip.gsub(' ', '').gsub(/[^\w-]/, '')
lastnamePart = self.last_name.downcase.strip.gsub(' ', '').gsub(/[^\w-]/, '')
login_part = firstnamePart+lastnamePart
new_login = login_part.dup
num = 1
while(User.where(:login => new_login).count > 0)
new_login = "#{login_part}#{num}"
num += 1
end
self.login = new_login
end
end
It did not work for me,but change:
while(User.find_by_login(new_login).count > 0)
to
while(User.where(login: new_login).count > 0)
Here's my methods that I use for Facebook
def ensure_username_uniqueness
self.username ||= self.email.split("#").first
num = 2
until(User.find_by(username: self.username).nil?)
self.username = "#{username_part}#{num}"
num += 1
end
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.username = auth.info.name.downcase.gsub(" ", "")
user.username = user.username[0..29] if user.username.length > 30
user.ensure_username_uniqueness
end
end
Here is the way how you can generate "username" from "full name"
def self.generate_username(full_name)
generated = ActiveSupport::Inflector.transliterate(full_name) # change ñ => n
.downcase # only lower case
.strip # remove spaces around the string
.gsub(/[^a-z]/, '_') # any character that is not a letter or a number will be _
.gsub(/\A_+/, '') # remove underscores at the beginning
.gsub(/_+\Z/, '') # remove underscores at the end
.gsub(/_+/, '_')
taken_usernames = User
.where("username LIKE ?", "#{generated}%")
.pluck(:username)
# username if it's free
return generated unless taken_usernames.include?(generated)
count = 2
while true
# username_2, username_3...
new_username = "#{generated}_#{count}"
return new_username if ! taken_usernames.include?(new_username)
count += 1
end
end

Resources