For this particular use case I have a method that gets all the sales opportunities for a user, for the inventory he has in stock. It's a loop that goes through the user's parts and then checks each part against the "reverse-auction" to make sure the auction is an opportunity and if it is, stick it in this array 'sales_opportunities'
So, in my Auction model I have the method:
def self.get_sales_opportunities(user)
parts = user.inventory_parts
parts.uniq! { |part| [part[:part_num], part[:condition]] }
sales_opportunities = []
parts.each do |part|
#stick auction in sales opportunities
#if the auction is not the user's, already contains a user bid,
#or if the auction isn't asking for the part in-question's condition
Auction.where(part_num: part.part_num, active: true).each do |auction|
user_created_auction = (auction.company == user)
user_has_placed_bids = (auction.bids & user.bids).present?
part_matches = auction.condition.include?(part.condition)
all_conditions = true if auction.condition[0].blank?
sales_opportunities << auction unless user_created_auction || user_has_placed_bids || !part_matches
sales_opportunities << auction if all_conditions && auction.company != user && !user_has_placed_bids
end
end
sales_opportunities.uniq
end
This is obviously a process and the load time is terrible..
How can I do this more efficiently?
Related
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
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
First, sorry for my English, I am totally new in ruby on rails even in very basic thing, so I hope you all can help me.
I have table Role and RoleUser
table Role have has_many relationship to RoleUser with role_id as foreign key
in table RoleUser is contain user_id, so I can call it 1 role have many users
and I want is to show all record in Role with additional field in every record called total_users,
total_users is in every record have role_id and count the user_id for every role, and put it in total_users,
I know this is must use the join table, but in rails I absolutely knew nothing about that, can you all give me a simple example how to do that.
and one more, same with case above, can I do for example Role.all and then the total_users in include in that without added it in database? is that use virtual column?
anyone have a good source of link to learn of that
I have following code in model
def with_filtering(params, holding_company_id)
order = []
if params[:sort].present?
JSON.parse(params[:sort]).each do |data|
order << "#{data['property']} #{data['direction']}"
end
end
order = 'id ASC' if order.blank?
if self.column_names.include? "holding_company_id"
string_conditions = ["holding_company_id = :holding_company_id"]
placeholder_conditions = { holding_company_id: holding_company_id.id }
else
string_conditions = []
placeholder_conditions = {}
end
if params[:filter].present?
JSON.parse(params[:filter]).each do |filter|
if filter['operation'] == 'between'
string_conditions << "#{filter['property']} >= :start_#{filter['property']} AND #{filter['property']} <= :end_#{filter['property']}"
placeholder_conditions["start_#{filter['property']}".to_sym] = filter['value1']
placeholder_conditions["end_#{filter['property']}".to_sym] = filter['value2']
elsif filter['operation'] == 'like'
string_conditions << "#{filter['property']} ilike :#{filter['property']}"
placeholder_conditions["#{filter['property']}".to_sym] = "%#{filter['value1']}%"
else
string_conditions << "#{filter['property']} = :#{filter['property']}"
placeholder_conditions["#{filter['property']}".to_sym] = filter['value1']
end
end
end
conditions = [string_conditions.join(' AND '), placeholder_conditions]
total_count = where(conditions).count
if params[:limit].blank? && params[:offset].blank?
data = where(conditions).order(order)
else
data = where(conditions).limit(params[:limit].to_i).offset(params[:offset].to_i).order(order)
end
return data, total_count.to_s
end
And I have follwing code in controllers
def crud_index(model)
data, total = Role.with_filtering(params, current_holding_company)
respond_to do |format|
format.json { render json: { data: data, total_count: total }.to_json, status: 200 }
end
end
My only purpose is to add virtual field called total_users, but i want added it in model and combine it with data in method with_filtering
If you have the models like this:
Class Role < ActiveRecord::Base
has_many :role_users
end
Class RoleUser < ActiveRecord::Base
belong_to :role
end
You could use select and joins to generate summary columns, but all the Role's attributes should be include in group.
roles = Role.select("roles.*, count(role_users.id) as total_users")
.joins(:role_users)
.group("roles.id")
Type those scripts in Rails console, Rails will generate a sql like :
SELECT roles.id, count(role_users.id) as total_users
FROM roles
INNER JOIN role_users
ON roles.id = role_users.role_id
GROUP BY roles.id
Then you can use roles.to_json to see the result. The summary column total_users can be accessed in every member of roles.
And there are many other way can match your requirement. Such as this. There is a reference of counter cache.
My suggestion is after searching, you can test those method by rails console, it's a useful tool.
UPDATE
According to OP's update and comment, seems you have more works to do.
STEP1: move with_filtering class method to controller
with_filtering handle a lot of parameter things to get conditions, it should be handled in controller instead of model. So we can transfer with_filtering into conditions and orders in controller.
class RolesController < ApplicationController
def conditions(params, holding_company_id)
if self.column_names.include? "holding_company_id"
string_conditions = ["holding_company_id = :holding_company_id"]
placeholder_conditions = { holding_company_id: holding_company_id.id }
else
string_conditions = []
placeholder_conditions = {}
end
if params[:filter].present?
JSON.parse(params[:filter]).each do |filter|
if filter['operation'] == 'between'
string_conditions << "#{filter['property']} >= :start_#{filter['property']} AND #{filter['property']} <= :end_#{filter['property']}"
placeholder_conditions["start_#{filter['property']}".to_sym] = filter['value1']
placeholder_conditions["end_#{filter['property']}".to_sym] = filter['value2']
elsif filter['operation'] == 'like'
string_conditions << "#{filter['property']} ilike :#{filter['property']}"
placeholder_conditions["#{filter['property']}".to_sym] = "%#{filter['value1']}%"
else
string_conditions << "#{filter['property']} = :#{filter['property']}"
placeholder_conditions["#{filter['property']}".to_sym] = filter['value1']
end
end
end
return [string_conditions.join(' AND '), placeholder_conditions]
end
def orders(params)
ord = []
if params[:sort].present?
JSON.parse(params[:sort]).each do |data|
ord << "#{data['property']} #{data['direction']}"
end
end
ord = 'id ASC' if ord.blank?
return ord
end
end
STEP2: update action crud_index with conditions and orders to get total_count of Roles.
class AnswersController < ApplicationController
def crud_index(model)
total = Role.where(conditions(params, current_holding_company)).count
if params[:limit].blank? && params[:offset].blank?
data = Role.where(conditions(params, current_holding_company)).order(orders(params))
else
data = Role.where(conditions(params, current_holding_company)).limit(params[:limit].to_i).offset(params[:offset].to_i).order(orders(params))
end
respond_to do |format|
format.json { render json: { data: data, total_count: total }.to_json, status: 200 }
end
end
end
STEP3: update action crud_index to get total_users by every role.
Make sure the two previous steps is pass the test.
class AnswersController < ApplicationController
def crud_index(model)
total = Role.where(conditions(params, current_holding_company)).count
if params[:limit].blank? && params[:offset].blank?
data =
Role.select(Role.column_names.map{|x| "Roles.#{x}"}.join(",") + " ,count(role_users.id) as total_users")
.joins(:role_users)
.group(Role.column_names.map{|x| "Roles.#{x}"}.join(","))
.where(conditions(params, current_holding_company))
.order(orders(params))
else
data =
Role.select(Role.column_names.map{|x| "Roles.#{x}"}.join(",") + " ,count(role_users.id) as total_users")
.joins(:role_users)
.group(Role.column_names.map{|x| "Roles.#{x}"}.join(","))
.where(conditions(params, current_holding_company))
.order(orders(params))
.limit(params[:limit].to_i)
.offset(params[:offset].to_i).order(orders(params))
end
respond_to do |format|
format.json { render json: { data: data, total_count: total }.to_json, status: 200 }
end
end
end
NOTE: step3 may need you to modify conditions and orders method to generate column_name with table_name prefix to avoid column name ambiguous error
If you can make these steps through, I suggest you can try will_paginate to simplify the part of your code about total_count ,limit and offset.
With what you explained, you could do something like this:
class Role < ActiveRecord::Base
has_many :role_users
has_many :users
def total_users
self.users.count
end
end
So you just need to call the total_users method on roles object which should get you what you desire. Something like this:
Role.first.total_users
# this will give you the total users for the first role found in your database.
Hope it helps
You might want to watch this Railscast too:
#app/models/role.rb
Class Role < ActiveRecord::Base
has_many :role_users
has_many :users, -> { select "users.*", "role_users.*", "count(role_users.user_id) as total_users" }, through: :role_users
end
This will allow you to call:
#roles = Role.find params[:id]
#roles.users.each do |role|
role.total_users
end
You can see more about how this works with a question I wrote some time ago - Using Delegate With has_many In Rails?
--
It's where I learnt about Alias columns, which Ryan Bates uses to count certain values:
Hey I am trying to make a method polymorphic so that it can access different models associations, but treat them the same
module Unitable
# Adds new units without duplication
def add_units(type, units = {})
if type.downcase == "army"
relationships = self.members
elsif type.downcase == "character"
relationships = self.owns
end
units.each do |name, amount|
unit = Unit.find_by(name: name)
if relationships.where(unit_id: unit.id).first.nil?
relationships.create(unit_id: unit.id, amount: amount) #<--- This is where the error occurs
else
relationship = relationships.find_by(unit_id: unit.id)
original_amount = relationship.amount
new_amount = amount + original_amount
relationship.update_attribute(:amount, new_amount)
end
end
end
end
This throws
ERROR: current transaction is aborted, commands ignored until end of transaction block
This is the root method
def gen_starting_units
credit = 9
#transaction do
units = Unit.tagged_with(["#{race}", "1"]).sample(2)
while credit > 0
unit = units.sample
if unit.cost <= credit
self.army.add_units("army", unit.name => 1)
credit -= unit.cost
end
end
#end
end
I call this method (with helper method detailed below as well), defined in User.rb model
def get_random_items
return nil unless groups_as_member
if groups_as_member == 1
assignments = groups_as_member.assignments.limit(5)
random_items = Post.rand_by_post(assignments)
end
random_groups = groups_as_member.sort_by{rand}.slice(0,5)
random_items = Array.new
i=0
return unless random_groups
until i == 10 do
random_groups.each do |group|
assignments = group.assignments.limit(5)
if y = Post.rand_by_post(assignments)
random_items << y
i+=1
if random_items == 5
return random_items
end
else
return random_items
end
end
end
return random_items
end
helper method rand_by_post in Post.rb
def self.rand_by_post(assignments)
find_by_id(assignments.rand.post_id)
end
in the user controller show action:
def show
#public_groups = Group.public
#groups_member = #user.groups_as_member
#groups_as_owner = #user.groups_as_owner
#random_items = #user.get_random_items
end
when I comment out the call in the user show action, the user show works fine on my development and production server. But when I try to user the method the servers will just hang there, not doing anything. I can't find any errors in the server logs or the heroku logs. My test writing skills are pretty limited, and I am having trouble writing one for the entire method.
Can anyone spot a problem?
if your random_groups is empty, your helper method get_random_items will go into an endless loop until i == 10 do ... end. That could be the reason.
You might want to change return unless random_groups to return if random_groups.empty?