How to use multiple #select blocks in Ruby? - ruby-on-rails

Let's say I have a list of records like:
transactions = Transaction.all
And I have the following instance methods #currency, #geo, #industry. I want to select records which has the following criteria:
Select all transactions that has field currency which equals to #currency unless #currency is nil and in this case we'll ignore the condition (currency would mean all currencies when it's nil)
Select all transactions that has field geo which equals to #geo unless #geo is nil.
Select all transactions that has field industry which equals to #industry unless #industry is nil.
I tried multiple #select but with no luck something like:
transactions.select{ |i| (i.currency == #currency) unless #currency.nil? }.
.select{ |i| (i.geo == #geo) unless #geo.nil? }.
.select{ |i| (i.industry == #industry) unless #industry.nil? }

The problem with your example is the unless #currency.nil? will return nil (which is falsey) if #currency is nil, which is the opposite of what you intended.
You should use || instead:
transactions.select{ |i| (i.currency == #currency) || #currency.nil? }.
select{ |i| (i.geo == #geo) || #geo.nil? }.
select{ |i| (i.industry == #industry) || #industry.nil? }
In this case, if #currency is nil, the first condition will return true, and all elements will pass the select box to the next one...
Another option would be to run the select block only is the parameter is not nil. In this case, you'd like to break the line into separate blocks:
transactions.select!{ |i| (i.currency == #currency) } unless #currency.nil?
transactions.select!{ |i| (i.geo == #geo) } unless #geo.nil?
transactions.select!{ |i| (i.industry == #industry) } unless #industry.nil?

transactions.select do |t|
(#currency.nil? || t.currency == #currency) &&
(#geo.nil? || t.geo == #geo) &&
(#industry.nil? || t.industry == #industry)
end
this should do the job.
Or, if you are into dynamics:
[:currency, :geo, :industry].all? do |field|
(ivar = instance_variable_get("##{field}")).nil? || t.send(field) == ivar
end

Use AR/SQL instead of Ruby processing when possible:
transactions.where(currency: #currency, geo: #geo, industry: #industry)

Multiple use of select is superfluous in this situation. You can use && and || logical operators:
transactions.select do |transaction|
(#currency.nil? || transaction.currency == #currency) &&
(#geo.nil? || transaction.geo == #geo) &&
(#industry.nil? || transaction.industry == #industry)
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.

Ruby array reject elements based on condition

I am trying to reject array items based on multiple conditions.
The code is as follows
def fetch_items
items= line_items.reject(&driving?)
if new_order_history_enabled?
items = items.reject{ |li| li.expenses == 0 }
end
items
end
def driving?
proc { |line_item| LineItemType.new(line_item, segment).drive? }
end
Is there a one liner or a more cleaner way to write this?
Something like
items= line_items.reject { |li| li.driving? && ( new_order_history_enabled? && li.expenses == 0)}
items= line_items.reject { |li| li.driving? || (new_order_history_enabled? && li.expenses == 0)}
Since you want both to apply here, I think you should use || instead of &&
That way, you are actually doing what you describe in your method. (and you only iterate once over the array, which is cool :) )
Although, and this is stylistic preference. I would prefer to do:
items = line_items.reject do |li|
li.driving? ||
(new_order_history_enabled? && li.expenses == 0)
end
since it might be clearer at a glance what we are doing
Personally I don't think a one-liner is always cleaner, especially when it's a long one-liner. The style that (to me) is cleaner, is to write:
def fetch_items
items= line_items.reject(&:driving?)
items= items.reject(&:zero_expenses?) if new_order_history_enabled?
end
def driving?
proc { |line_item| LineItemType.new(line_item, segment).drive? }
end
# in the LineItem class, define the zero_expenses? method:
def zero_expenses?
expenses.zero?
end

Simplify if else condition ruby

I have a if else condition in my ruby code listed below:
if !category.nil?
return false unless company_matches.any? { |w|
comparison = /(\s|^)#{w}(\s|$)/i
(title.index(comparison) || description.index(comparison) || clean_title.index(comparison) || clean_desc.index(comparison)) && (category == 'Business')}
else
return false unless company_matches.any? { |w|
comparison = /(\s|^)#{w}(\s|$)/i
(title.index(comparison) || description.index(comparison) || clean_title.index(comparison) || clean_desc.index(comparison))}
end
How can i simplify this to make it look more subtle?
company_matches.any? do |company|
[title, description, clean_title, clean_desc].any? do |attribute|
attribute.match? /(\s|^)#{company}(\s|$)/i
end && (category == 'Business' || category.nil?)
end
You can create a method to do your comparisons so you don't repeat yourself. You can also abstract the comparison variable out of the if .. else block. Here's what I have:
comparison = /(\s|^)#{w}(\s|$)/i
result = perform_comparison(title, description, clean_title, clean_desc, comparison)
unless category.nil?
return false unless company_matches.any? { |w| result && (category == 'Business')}
else
return false unless company_matches.any? { |w| result }
end
# somewhere else in your code
def perform_comparison(title, description, clean_title, clean_desc, comparison)
title.index(comparison) || description.index(comparison) || clean_title.index(comparison) || clean_desc.index(comparison)
end

ruby regex to find nil or blank values for import csv?

row = {"joining_date"=>"18/07/2015", "name"=>" Joe Doe ", "company"=>" Google", "location"=>" New York ", "role"=>"developer", "email"=>"joe#doe.com", "mobile"=>"11-(640)123-45674", "address"=>"4 XYZ Road", "validity"=>"true"}
row is invalid only if any one of the fields(joining_date, name, company, location, email, address) is nil or not present.
def is_valid?
valid = true
if row[:name] == nil || row[:joining_date] == nil || row[:address] == nil || row[:email] == nil || row[:company] == nil || row[:location] == nil
valid = false
end
valid
end
Is there any way that I can simplify and refactor the above method in rails to find it more efficient using regex?
Probably, but I wouldn't use a regex as it's in a hash. As you're using rails you can use present? or blank?.
row.values.any?(&:blank?)
Would return true if any are blank
for your case
def is valid?
row.values.all?(&:present?)
end

Resources