Ruby: Looping through models to calculate ratios within nested hashes - ruby-on-rails

I'm building a spam filter for a job app (think tinder for jobs). I'm currently helping to build a spam filter. To achieve that goal, a signal for users who are "over-applying" for jobs is their apply-to-rejection ratio per day.
To inform our threshold, I've come up with a solution to gather that data from the db by using a nested hash i.e. {user1 =>{date1=>0.33, date2=>0.66}}. My problem now is that the ratios are all 1.0, because i think i'm looping up until either rejections or applications are all gone through so the calculation is always the same number divided by itself.
Here's what i got so far. Appreciate the help.
users = User.all
ratio_hash = Hash.new
users.each do |user|
if user.job_applications.count > 0 && user.job_rejections.count > 0
ratio_hash[user.name] = Hash.new
apply_array = []
reject_array = []
user.job_rejections.each do |reject|
user.job_applications.each do |apply|
if (apply.user_id.present? && reject.user_id.present?) || rej.user_id.present?
if (apply.user_id == user.id && reject.user_id == user.id) || rej.user_id == user.id
if (apply.created_at.present? && reject.created_at.present?) || reject.created_at.present?
date = (apply_array && reject.created_at.to_date)
if (apply.created_at.to_date == reject.created_at.to_date) || reject.created_at.to_date == date
apply_array << apply.created_at.to_date
reject_array << reject.created_at.to_date
ratio_hash[user.name][(apply.created_at.to_date || reject.created_at.to_date)] = (apply_array.length.round(2)/reject_array.length)
end
end
end
end
end
end
end
end

Related

Simplifying method

I have this method which works fine but I'm thinking that it may be improved, either for readability and/or efficience.
def default_something
something =
Legal::Something.find_for_A_in_placea('xx', claim.blabla.identifier) ||
Legal::Something.find_for_B_in_placeb('xx', claim.eligible.first.bleble.indivcode) ||
Legal::Something.find_for_B_in_placeb('xx', claim.eligible.last.blublu.indivcode) ||
Legal::Something.find_by(id: DEFAULT_SOMETHING_ID)
{
'name' => something.name,
'day' => something.meta_data[:day],
'hour' => something.meta_data[:hour],
}
end
I can "beautify it" by creating some more methods like:
def default_something
something = def A || def B || (etc)
end
def A
Legal::Something.find_for_A_in_placea('xx', claim.blabla.identifier)
end
def B
Legal::Something.find_for_B_in_placeb('xx', claim.eligible.first.bleble.indivcode) ||
Legal::Something.find_for_B_in_placeb('xx', claim.eligible.last.blublu.indivcode)
end
In addition I should say:
find_for_B part only retrieves a value when claim.eligible.first.bleble.indivcode || claim.eligible.last.blublu.indivcode = 'ASL'
Is the "beautified" version the way to go?
And/or should I add an if statement regarding
Legal::Something.find_for_B_in_placeb('xx', claim.eligible.first.bleble.indivcode) ||
Legal::Something.find_for_B_in_placeb('xx', claim.eligible.last.blublu.indivcode)
to improve efficiency and readability, stating it happens only when "indivcode" = "ASL"?
What else can I do?

Sort membership tiers by names on index page

So I have a drivers (user table) which has a relationship with the subscriptions table. There are 3 different tiers available: Gold, Silver and a Free tier. What i want to do is group and order by tiers, so I'd have the golds together, silvers together etc in descending order.
What i have now in my controller:
class DriversController < ApplicationController
def index
order_subs = Driver.order_by_subs.all
def gold_drivers
Driver.select { |driver| driver.subscriptions.stripe_plan == 'price_xxx' || driver.subscriptions.stripe_plan == 'price_xxx'}
end
def silver_drivers
Driver.select { |driver| driver.subscriptions.stripe_plan == 'price_xxx' || driver.subscriptions.stripe_plan == 'price_xxx'}
end
def free_drivers
Driver.select { |driver| driver.subscriptions == 'null' || driver.subscriptions == ''}
end
#pagy, #drivers = pagy(
Driver.joins(:profile).select(
'drivers.*',
'(profiles.no_races + profiles.no_poles + profiles.no_podiums + profiles.no_wins) AS score'
).reorder(gold_drivers, silver_drivers, free_drivers, score),
page: params[:page],
items: 16
)
end
end
So my thoughts were I could select the records under a variable i.e gold_drivers and then add them as I would in the reorder section in the #pagy pagination section .reorder(gold_drivers, silver_drivers, free_drivers, score) At the moment when i run the page I get the error undefined method stripe_plan' for nil:NilClass` so i'm guessing it can't find the column. If it's a free user, they won't have a record in the subscription table. Thanks
EDIT: driver model
scope :is_gold, -> { where("drivers.subscriptions.stripe_plan == 'price_xxx'") || where("drivers.subscriptions.stripe_plan == 'price_xxx'") }
scope :is_silver, -> { where("drivers.subscriptions.stripe_plan == 'price_xxx'") || where("drivers.subscriptions.stripe_plan == 'price_xxx'") }
scope :is_null, -> { where("drivers.subscriptions.stripe_plan == ''") || where("drivers.subscriptions.stripe_plan" == null) }
scope :order_by_subs, -> { reorder(:is_gold, :is_silver, :is_null) }
I have to be honest your code is incredibly confusing. It looks like you can order by subs, but I guess you can't, I'm not sure what is working and what isn't looking at your code. I see what you are doing from our previous conversation and this is off. Like I said in my comment, I would focus on understanding the basics here before you dive into some of this stuff. I'm going to fix your method and explain along the way so it hopefully makes some more sense and either works, or shows you a path to getting it to work.
class DriversController < ApplicationController
def index
# because there is no # on this it is a local variable, it will not be available in the view. Wondering also why you didn't just use this for the select below. Also, if you can already order by subs why would you even need to select them, wasn't that the reason for selecting them like this in the first place?
order_subs = Driver.order_by_subs.all
# it is my understanding you want these in the view so we add the # symbol which makes it an instance variable and you access those variables in the view now
#gold_drivers = order_subs.select { |driver| driver.subscriptions.stripe_plan == 'price_xxx' || driver.subscriptions.stripe_plan == 'price_xxx'}
#silver_drivers = order_subs.select { |driver| driver.subscriptions.stripe_plan == 'price_xxx' || driver.subscriptions.stripe_plan == 'price_xxx'}
#free_drivers = order_subs.select { |driver| driver.subscriptions == 'null' || driver.subscriptions == ''}
# Not sure where your local variable 'score' is coming from, do you need to set that first?
#pagy, #drivers = pagy(
Driver.joins(:profile).select(
'drivers.*',
'(profiles.no_races + profiles.no_poles + profiles.no_podiums + profiles.no_wins) AS score'
).reorder(#gold_drivers, #silver_drivers, #free_drivers, score),
page: params[:page],
items: 16
)
end
end
O.k so now you are setting the drivers values as instance variables which can be accessed in your view.

Clean up messy code that query's based on multiple options

I'm using Rails, but the underlying question here applies more broadly. I have a report page on my web app that allows the user to specify what they're filtering on, and query the database based on those filters (MongoDB).
The data is based around hotels, the user must first select the regions of the hotels (state_one, state_two, state_three), then the statuses of the hotels (planning, under_construction, operational), then an optional criteria, price range (200, 300, 400). Users can select multiple of each of these options.
My way of doing this currently is to create an empty array, iterate through each region, and push the region into the array if the user selected that region. Then, I'm iterating through THAT array, and assessing the status of the hotels in those regions, if any hotel has the status the user has selected, then I'm adding that hotel to a new empty array. Then I do the same thing for price range.
This works, but the code is offensively messy, here's an example of the code:
def find_hotel
hotels = find_all_hotels
first_array = []
hotels.each do |hotel|
if params[:options][:region].include? 'state_one' and hotel.state == :one
first_array.push(hotel)
elsif params[:options][:region].include? 'state_two' and hotel.state == :two
first_array.push(hotel)
elsif params[:options][:region].include? 'state_three' and hotel.state == :three
first_array.push(hotel)
end
end
second_array = []
first_array.each do |hotel|
if params[:options][:region].include? 'planning' and hotel.status == :planning
first_array.push(hotel)
elsif params[:options][:region].include? 'under_construction' and hotel.status == :under_construction
first_array.push(hotel)
elsif params[:options][:region].include? 'operational' and hotel.status == :operational
first_array.push(hotel)
end
end
third_array = []
second_array.each do |hotel|
# More of the same here, this could go on forever
end
end
What are some better ways of achieving this?
How about this:
STATES = [:one, :two, :three]
STATUSES = [:planning, :under_construction, :operational]
PRICES = [200, 300, 400]
def find_hotel
region = params[:options][:region]
first_array = set_array(region, find_all_hotels, STATES, :state)
second_array = set_array(region, first_array, STATUSES, :status)
third_array = set_array(region, second_array, PRICES, :price_range)
end
def set_array(region, array, options, attribute)
array.each_with_object([]) do |element, result|
options.each do |option|
result << element if region.include?(option) && element[attribute] == option
end
end
end
UPDATE
Added attribute parameter to set_array in order to make the code work with your updated example.
Since second_array is empty, whatever you get by iterating over it (perhaps third_array) would also be empty.
def find_hotel
hotels = find_all_hotels
first_array = hotels
.select{|hotel| params[:options][:region].include?("state_#{hotel.state}")}
first_array += first_array
.select{|hotel| params[:options][:region].include?(hotel.status.to_s)}
second_array = third_array = []
...
end

Filter an array with many parameters "like an SQL query"

I need to filter objects in array.
It works with one parameters
#usersc = #usersb.select { |user| user.need_appartment? }
but i would like use more parameters than in SQL/ActiveRecord :
(need_bedrooms_min >= :nb_bedrooms_min) AND (budget_amount BETWEEN :budget_min AND :budget_max) AND ((need_surface_min BETWEEN :surface_min AND :surface_max) OR (need_surface_max BETWEEN :surface_min AND :surface_max))"+req,{nb_bedrooms_min: params[:nb_bedrooms_min], budget_min: params[:budget_min], budget_max: params[:budget_max],surface_min: params[:surface_min], surface_max: params[:surface_max]}).paginate(:page => params[:page])
I dont find the solution... Anyone can help me ?
F.
select does exactly what you need with as many parameters as you might want:
#usersb.select do |user|
user.need_bedrooms_min >= params[:nb_bedrooms_min].to_i &&
(params[:budget_min].to_i..params[:budget_max].to_i).include? user.budget_amount &&
((params[:surface_min].to_i..params[:surface_max].to_i).include? user.need_surface_min ||
(params[:surface_min].to_i..params[:surface_max].to_i).include? user.need_surface_max)
end
Or, more cleanly:
class User
def needs_apartment?(params)
budget_min, budget_max, surface_min, surface_max, nb_bedrooms_min =
%w{budget_min budget_max surface_min surface_max nb_bedrooms_min}.map{|k| params[k.to_sym].to_i}
budget_range = budget_min..budget_max
surface_range = surface_min..surface_max
need_bedrooms_min >= nb_bedrooms_min &&
budget_range.include? budget_amount &&
(surface_range.include?(need_surface_min) || surface_range.include?(need_surface_max))
end
end
#usersb.select{|user| user.needs_apartment?(params)}

Combining params in Rails

Hi im doing a filter section for "players" on my app..
Im filtering by "position" at this moment, but i need to filter by "birthday.year" in database the birthday is complete 1900-00-00
I actually do a good research before but i can "mix" or "combine" my params.. the best answer i found was here (so its not a duplicate)
Rails: combining optional params into a query
im a noob in Rails,so i will appreciate any help im just doing the integration of a design..
Here is my code but how can i use minyear, and maxyear to filter by position and age, for example..
thanks!!
def index
#candidates = Player.order("created_at DESC")
position = params[:position]
minyear = params[:minyear]
maxyear = params[:maxyear]
if position == 'goalkeeper'
#candidates = #candidates.where(position:'goalkeeper')
elsif position == 'cedefense'
#candidates = #candidates.where(position:'cedefense')
elsif position == 'ridefense'
#candidates = #candidates.where(position:'ridefense')
elsif position == 'ledefense'
#candidates = #candidates.where(position:'ledefense')
elsif position == 'defmedium'
#candidates = #candidates.where(position:'defmedium')
elsif position == 'ofemedium'
#candidates = #candidates.where(position:'ofemedium')
elsif position == 'rimedium'
#candidates = #candidates.where(position:'rimedium')
elsif position == 'lemedium'
#candidates = #candidates.where(position:'lemedium')
elsif position == 'offensive'
#candidates = #candidates.where(position:'offensive')
elsif position == 'scoach'
#candidates = #candidates.where(position:'scoach')
elsif position == 'sprepf'
#candidates = #candidates.where(position:'sprepf')
else
#candidates = Player.all
end
After a lot of research i come up with this
position = params[:position]
minyear = params[:minyear]
maxyear = params[:maxyear]
if params[:position].nil?
#candidates = Player.all
elsif !params[:position].nil? && params[:minyear].nil?
#candidates = #candidates.where("position = ?", position)
elsif !params[:minyear].nil?
#candidates = #candidates.where("position = ? and birthday = ?", position, minyear )
else
#candidates = Player.all
end
The only problem now is that birthday as i said before has a full format, im just interested just in the year... how can i solve this?
thanks in advance
Nevermind it works like this
#candidates = #candidates.where("position = ? and birthday < ?", position, minyear )
Thanks to Alex D i ha this now,
#candidates = Player.scoped # for Rails 3
if params[:position].present?
#candidates = #candidates.where(position: position)
end
if year = params[:year]
date = Date.new(year)
# this will find all people whose birthday is within the given year
# using YEAR(birthday) will likely cause a full table scan;
# it's better to use a range query
#candidates = #candidates.where("birthday >= ? AND birthday <= ?", Date.new(minyear), Date.new(maxyear).end_of_year)
end
First of all, you can improve your code if you know that Active Record's where and other similar methods are chainable:
#candidates = Player.order('created_at DESC')
# if you don't want to set a default order, you can use Player.scoped in Rails 3
# I forget what it is for Rails 4. Maybe just Player.all.
# In Rails 3, .all returns an Array, which doesn't allow you to chain additional
# where conditions, etc.
if params[:position].present?
#candidates = #candidates.where(position: position)
end
if params[:minyear].present?
#candidates = #candidates.where(birthday: minyear)
end
Now the second part: you actually want to match on the birthday year. There are a couple ways to do this, but this is the way which can benefit from database indexes if you have the right indexes in place:
if year = params[:year]
date = Date.new(year)
# this will find all people whose birthday is within the given year
# using YEAR(birthday) will likely cause a full table scan;
# it's better to use a range query
#candidates = #candidates.where("birthday >= ? AND birthday <= ?", date, date.end_of_year)
end
Since your param is called minyear, I'm guessing you may actually want all the people whose birthday is during the given year or later. In that case:
#candidates = #candidates.where("birthday >= ?", Date.new(year))
Or if it's minyear and maxyear:
#candidates = #candidates.where("birthday >= ? AND birthday <= ?", Date.new(minyear), Date.new(maxyear).end_of_year)
#candidates = #candidates.where("position = ? and year(birthday) < ?", position, minyear )
Try it, this will serve your purpose.

Resources