DRY function in a better way - ruby-on-rails

could you tell me a way to write this code in a better way ?
Create an user with data attributes fetched from facebook .
User.create do |user|
user.email = data['email']
user.password = Devise.friendly_token
user.facebook_uid = data['facebook_uid']
user.first_name = data['first_name']
user.last_name = data['last_name']
user.gender = data['gender']
user.timezone = data['timezone']
user.birthday = data['birthday']
user.link = data['link']
user.locale = data['locale']
user.picture_url = 'https://graph.facebook.com/' + data['facebook_uid'] + '/picture?type=large'
user.fb_access_token = access_token
end

hash['picture_url'] = something
User.create(hash)

Solution 1
User.create do |user|
data.each do |k, v|
user.send("#{k}=", v) if User.columns_hash.has_key?(k)
end
user.fb_access_token = access_token
user.password = Devise.friendly_token
user.picture_url = "https://graph.facebook.com/%s/picture?type=large" %
data['facebook_uid']
end
Solution 2
hash = {}
data.each do |k, v|
hash[k]= v if User.columns_hash.has_key?(k)
end
hash['fb_access_token'] = access_token
hash['password'] = Devise.friendly_token
hash['picture_url'] = "https://graph.facebook.com/%s/picture?type=large" %
data['facebook_uid']
User.create(hash)

ActiveRecord's create method can take a hash of attributes. Since your hash keys match the accessor methods exactly, you can just pass in User.create(hash) after setting data['picture_url'] the same way you did before. Tass's answer is essentially correct, but to more precisely match your code:
data['picture_url'] = 'https://graph.facebook.com/' + data['facebook_uid'] + '/picture?type=large'
User.create(data)

Related

Ruby on Rails JSON to Model Conversion

def create
#match_data = JSON.parse(request.raw_post)
#match = #match_data["match"]
match = Match.new
match.match_length = #match["match_length"]
match.quest = #match["quest"]
match.humans_team_stats = #match["humans_team_stats"]
match.supernaturals_team_stats = #match["supernaturals_team_stats"]
#match_data["users_match_stats"].each do |user|
user_account = User.find user["id"]
match.users << user_account
match.save!
user_match_stats = UserMatchStat.new
user_match_stats.user = user_account
user_match_stats.match = match
user_match_stats.kills = user["kills"]
user_match_stats.deaths = user["deaths"]
user_match_stats.assists = user["assists"]
user_match_stats.damage_dealt = user["damage_dealt"]
user_match_stats.damage_taken = user["damage_taken"]
user_match_stats.first_blood = user["first_blood"]
user_match_stats.save!
match.user_match_stats << user_match_stats
end
render json: #match_data
end
is this literally the best way to map an incoming json object to a rails model and make a record for it? There's gotta be a better way than this...
Provided your model's columns are the same as the keys in the JSON object (which it looks like they are from your code snippet), you can use symbolize_keys to turn the json objects into a ruby hash that your rails model will accept as commit params. It could look something like the following:
match = Match.create(match_length: #match["match_length"], quest: #match["quest"], humans_team_stats: #match["humans_team_stats"], supernaturals_team_stats: #match["supernaturals_team_stats"])
#match_data["users_match_stats"].each do |user|
user_account = User.find user["id"]
match.users << user_account
UserMatchStat.create(user.symbolize_keys)
end

Add a random string while updating attribute

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

Storing data from Twitter API in Rails

Before I begin, I'm still learning the whole MVC frame work so please be understanding in your explanations.
I am making a call to the Twitter API using the Twitter Gem. Im gathering all of my followers and returning their names and gender using the genderize.io ruby gem extension. I am displaying the names and percentage of genders "male, female, unknown" on my index view.
My final bit of code returns the breakdown into a JSON object.
"{\"male\":{\"count\":59,\"percent\":0.46},\"female\":{\"count\":31,\"percent\":0.24},\"unknown gender\":{\"count\":38,\"percent\":0.3}}"
My question is how do I refactor this long index method and break this down so that the model can store the data that I am getting back from the API and the gender breakdown? In other words I need to store the Screename (screen_name), list of followers (#user_name), and gender percentage (#gender_percentage).
If anyone has any idea of how to begin breaking this down and where to being storing the data please let me know
My code is displayed here.
class TwitterController < ApplicationController
require 'twitter'
require 'json'
def index
client = Twitter::REST::Client.new do |config|
config.consumer_key = '************'
config.consumer_secret = '************'
config.access_token = '************'
config.access_token_secret = '************'
end
screen_name = 'myscreenname'
follower_ids = client.follower_ids(screen_name)
user_name = []
follower_ids.each_slice(100) do |slice|
begin
client.users(slice).each do |user|
user_name << user.name
end
end
end
#user_name = user_name
c = user_name.map{|str| str.split.first}
gen = []
gir = GenderizeIoRb.new
c.each do |res|
begin
res = gir.info_for_name(res)
gen << "#{res[:result].gender}"
rescue
res = "GenderizeIoRb::Errors::NameNotFound"
gen << "uknown gender"
end
end
hash = Hash.new 0
gen.each do |mfg|
hash[mfg] += 1
end
count = hash.to_a
sum = 0
count.each { |_, v| sum += v }
gender_percentage = []
count.each do |k, v|
gender_percentage << [k, ((v.to_f / sum) * 100).round]
end
#gender_percentage = gender_percentage
total = count.map { |elt| elt[1] }.reduce { |x, y| x + y }
hsh = {}
count.each do |label, n|
hsh[label] = {}
hsh[label]['count'] = n
hsh[label]['percent'] = (n.to_f / total.to_f).round(2)
end
hsh.to_json
end
end

inject method to retrieve data from hash

I'm having trouble getting the method below in my user model to handle a hash ('auth') I'm getting from LinkedIn for user signin:
def self.deep_get auth, *fields
auth.inject(auth) { |acc, e| acc[e] if acc }
end
I call the 'deep_get' method later in my user model as I create a user using omniauth/linkedin gem. However, it's returning nil values for the provider/uid/headline/email user fields that I know are not nil.
I included first_name and last_name fields as an example because this approach is working (not returning nil values), but (as I realize) bad style/exception handling. Any ideas as to why my deep_get inject method isn't working to retrieve the data in the hash as I'd like it to?
def self.create_from_omniauth(auth)
create! do |user|
# i'd like to retrieve user information from linkedin per the following with my inject method, but i am getting nil values when i should be getting data.
# :provider and :uid are on the same branch level of data. first_name,last_name,email,etc. are on a branch just below called 'info'
user.provider = deep_get(auth, :provider)
user.uid = deep_get(auth, :uid)
user.headline = deep_get(auth, :info, :headline)
user.email = deep_get(auth, :info, :email)
# the below is working but i know pokemon exception handling is not good style.
begin
user.first_name = auth["info"]["first_name"]
rescue
end
begin
user.last_name = auth["info"]["last_name"]
rescue
end
try this
def deep_find(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=deep_find(a.last,key) }
r
end
end
or try this
class Hash
def deep_fetch(key, default = nil)
default = yield if block_given?
(deep_find(key) or default) or nil
end
def deep_find(key)
if key?(key)
self[key]
else
self.values.inject(nil) do |memo, v|
memo = v.deep_find(key) if v.respond_to?(:deep_find)
memo unless memo.nil?
end
end
end
end

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