Rails order active records with assign_attributes - ruby

I have a User model which has a Scoring model which has a score value.
In my rails view I want to make an order of my users by score.
=> User.joins (: scoring) .order (: score)
So far, so good.
it gets complicated when I would dynamically change the score of some User without modifying them in the database according to certain attributes such as geolocation.
I tried the assign_attributes function but it does not change because the .order function calls the score fields in the database.
Use case: I do a user search by geolocation and the users near the geolocation appear in my search with their scores. I would like to weight the scores of users nearby since they are not on the exact geolocation
My code:
#Get scoring in other geolocation
#fiches_proxi = Fiche.joins(:user).merge(User.joins(:scoring)).near([#geo.lat_long_DMS.to_f, #geo.lat_long_grd.to_f], proxi_calcule(#geo.population_2012.to_i),units: :km, :order => 'scorings.score DESC').order('scorings.score DESC').where.not(geo: #geo.id).limit(10)
#Get scoring in real geolocation
#fiche_order_algo_all = Fiche.joins(:user).merge(User.joins(:scoring)).where(geo_id: #geo)
#Find all scores
#fiches_all = Fiche.where(id: #fiche_order_algo_all.pluck(:id) + #fiches_proxi.pluck(:id))
#pagy, #fiche_order_algo = pagy(#fiches_all.joins(:user).merge(User.joins(:scoring).order('scorings.score DESC')), items: 12)
#fiche_order_algo.each do |f|
if f.geo.id != #geo.id
f.user.scoring.assign_attributes(score: (f.user.scoring.score - 10.0))
else
f.user.scoring.score
end
end
My score is updated but my order is the same !

When you call .each on your relation, it returns an array, so you can use Array#sort_by
#fiche_order_algo.each do |f|
if f.geo.id != #geo.id
f.user.scoring.assign_attributes(score: (f.user.scoring.score - 10.0))
else
f.user.scoring.score
end
end
#fiche_order_algo.sort_by!{|f| f.scoring.score}
If you're working with large data sets, this might not be optimized, but won't be any less efficient than what you already have.
But you can also do it in one go with:
#fiche_order_algo.sort_by! do |f|
if f.geo.id != #geo.id
f.user.scoring.assign_attributes(score: (f.user.scoring.score - 10.0))
end
f.user.scoring.score
end

Related

Understanding individual users over multiple username changes using username change records only (no unique ID exists)

I have a graph theory problem which involves analysing users over multiple username changes (a unique ID for each user has not been kept unfortunately).
There is a list of username changes and the time during which that username was changed. The format is: '2017-01-01 02:00:00', 'previous_username', 'new_username'.
The objective is to link each user's most up to date username for a given point in time. For example, I would like to be able to answer the question: what is the current username of the user who had the username 'previous_username' on '2016-12-31 00:00:00'?
I recognise that this is a graph theory problem and I am looking for solve it in python. Please note that user's might have changed their usernames multiple times.
This problem is not graph theory problem. You are looking for data structure that can provide queries about usernames. To have fast implementation of searches of this kind, some indexing is needed.
A simple solution is to have data structure that:
for each user stores list of it's past usernames with time duration when username was used,
maps every used username to list of positions where it is used in user lists.
With that index in user list is user's unique ID.
Something like this (not tested at all):
from collections import defaultdict
class LogData:
def __init__(self):
self.users = []
self.usernames = defaultdict(list)
# Note: data should be filled in time sorted order!
def add(self, previous_username, new_username, time):
# Find is username know
for i, username_list in enumerate(self.users):
if username_list[-1]['username'] == previous_username:
username_list[-1]['to_time'] = time
self.usernames[new_username].append((i, len(username_list)))
username_list.append(dict(username=new_username, from_time=time))
return
# First apearance of previous_username
self.usernames[previous_username].append((len(self.users), 0))
self.usernames[new_username].append((len(self.users), 1))
self.users.append([
dict(username=previous_username, to_time=time),
dict(username=new_username, from_time=time),
])
def current_username(self, username, time):
for user_ind, i in self.usernames.get(username, []):
d = self.users[user_ind][i]
from_time = d.get('from_time')
to_time = d.get('to_time')
if (from_time is None or from_time <= time) and \
(to_time is None or to_time >= time):
return self.users[user_ind][-1]['username']
Well, you could see this is a graph theory problem. But I think the easiest way is to first order the list - let's call it username_changes - by time, and then traverse it similar to this example (haven't tested t
username_changes.sort(key = lambda x: x[0])
from_time = '2016-12-31 00:00:00'
user_name = 'previous_username'
for row in username_changes:
if row[0] >= from_time and row[1]==user_name:
user_name = row[2]
print(user_name)

Save API response data daily without overwriting

UPDATED
I have setup a model Graph with a single attribute, :data
I request, for eg, Facebook page and store the likes in data.
How can I schedule that API call everyday, such that two things are achievable
1. Value stored in :data is a hash of key - updated_at and value :data. This hash being added with value everyday without risk of overwriting.
2. All the :data stored everyday can be recalled in a page and presented like a monthly graph, or a calculation is done, for eg, to show avg likes per day, etc.
Controller
def index
#fb = Graph.fb_page("facebook")
#data = Graph.new(:fb => [#fb])
#data.save
end

Searching Hash for entry that does not contain value

I am not quite sure how to title this question and I am having a hard time getting it to work so here goes.
I have a hash of users which is various sizes. This can be anywhere from 2 to 40. I also have a hash of tickets which I want to search through and find any entries that do not contain the user_id of my users hash. I am not quite sure how to accomplish this. My last attempt I used this:
#not_found = []
users.each do |u|
#not_found += #tickets.select{|t| t["user_id"] != u.user_id}
end
I know this is not the right result as it does a comparison for only the one user_id. What I need to do is run through all of the tickets and pull out any results that contain a user_id that is not in the users hash.
I hope I am explaining this properly and appreciate any help!
Try this
known_user_ids = users.map(&:user_id)
tickets_with_unknown_users = #tickets.reject{ |t| known_user_ids.include?(t["user_id"]) }

ActiveRecord Update_all to variable value

I'm looking for a way to update all record with certain condition their cur_val + 100:
I have a table Suggestions with id and score fields, and I want all the entries with specific ids to receive a score bump, e.g:
Suggestion.where(id: ids_list).update_all(score: score+100)
How do I do that?
Try plain SQL, read about update_all:
Suggestion.where(id: ids_list).update_all('score = score + 100')
But remember update_all not trigger Active Record callbacks or validations.
Some tips:
You can do it in Ruby but this very bad:
Suggestion.where(id: ids_list).find_each do |x|
x.update(score: x.score + 100)
end
Things like this should happen in database.

Calculating an Average from booleans with Ruby on Rails

I have a setup in which there are 10 attributes that accept a float in a rails form. Each attribute also is associated with a value in my model. If a number is entered on the form for more than one attribute, I need to create a weighted average.
An example would be if I have 10 products, each having a price in my model. In the form, a user can enter in the amount (number of products) for each product. I'd like to calculate a weighted price for those products that have an amount entered.
So how can I create a weighted average that checks which products have amounts entered?
columns_names = ['a','b','c','d'] # array of name of the columns
obj = Model.find(:id) # find the object with id
# loop and get column values that are set
values = columns_names.inject([]) do |arr,column_name|
arr << obj.column_name if params[column_name].eql?"true" # collect the values if the column set
arr
end
#get average
if values.blank?
# no column selected
else
avg = values.reduce(:+)/values.size
end
check this for help on weighted average
http://www.dzone.com/snippets/weighted-mean
This code retrieves all attributes that are true on your model:
#model = Model.find(params[:id)
#model.attributes.select{|k,v| (v.is_a?(TrueClass) || v.is_a?(FalseClass)) && v}
If you want the false ones just do a:
#model.attributes.select{|k,v| (v.is_a?(TrueClass) || v.is_a?(FalseClass)) && !v}
Don't know if this is what you are looking for, but maybe it can clear your head a little bit.

Resources