Ruby on Rails: Organize big search method - ruby-on-rails

I have big search method in my model that corresponds to search proper Car.
It looks like this:
def self.search(params)
cars = joins(:reservations).where.not("reservations.reception_time <= ? AND reservations.return_time >= ?",
params[:return_date], params[:handover_date])
cars = joins(:car_class).where("car_classes.id= ?", params[:car_class])
cars = cars_at_both_locations(params[:handover_location], params[:return_location])
cars = params[:car_body_style] == [""] ? cars : joins(:car_configuration).
where("car_configurations.body_style_id = ?", params[:car_body_style])
cars = params[:car_fuel] == [""] ? cars : where(fuel: params[:car_fuel])
cars = params[:car_transmission] == [""] ? cars : where(transmission: params[:car_transmission])
cars = params [:car_seats] == [""] ? cars : car_seats(params[:car_seats])
cars = Car.joins(:prices).where('prices.to_days >= ?',
(Date.parse(params[:return_date]) - Date.parse(params[:handover_date])).to_i)
end
It is very unreadable...
Did anyone have any idea to refactor this method to be more readable?

You should use scopes with proper names for each method in your Car model
for example for your first two lines, add in your model
scope :available, ->(handover_date,return_date) { joins(:reservations).where.not("reservations.reception_time <= ? AND reservations.return_time >= ?",
return_date, handover_date)}
scope :with_class, ->(car_class_id) {where("car_classes.id= ?", car_class_id)}
You will then just have to write
def self.search(opts)
available(opts[:handover_date],opts[:return_date]).
with_class(opts[:car_class_id]).
[...]
end
You will also be able to use the scopes available and with class everywhere, which is pretty cool too.

Related

Activerecord query where current employer is X and previous employer is Y

Basically I'd like to return all people whose current job title is X and whose previous job title is Y. As an example, I have a talent whose current emnployment is "Airbnb (company_id = 1)" and whose previous employment is at "Youtube (company_id = 2)".
If I run a query to find talent where current employment is Airbnb:
Talent.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year = ?", 1, "Present"])
I get the person.
If I run a query where previous employment is Youtube (hence the end_year != "Present" below)
Talent.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year != ?", 2, "Present"])
I also get the same person.
However, if I chain them together to find talents where current employer is Airbnb AND previous employer is Youtube, like this:
#talents = Talent.all
#talents = #talents.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year = ?", 1, "Present"])
#talents = #talents.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year != ?", 2, "Present"])
I do not get any results. I've tried several variations of the query but none return anything.
The only way I can get it to work is by using the first query and then looping over each talent to find where job_histories.company_id == 2.
if params[:advanced_current_company] && params[:advanced_previous_company]
#talents = #talents.joins(:job_histories).where(job_histories: { company_id: params[:advanced_current_company] }).distinct if params[:advanced_current_company]
#talents.each do |talent|
talent.job_histories.each do |job_history|
if job_history.company_id == params[:advanced_previous_company][0].to_i
new_talents.append(talent.id)
end
end
end
#talents = Talent.where(id: new_talents)
end
Any direction would be amazing. Thanks!
You had the right idea with a double join of the job_histories, but you need to alias the job_histories table names to be able to differentiate between them in the query, as otherwise activerecord will think it's only one join that needs to be done.
Talent.joins("INNER JOIN job_histories as jh1 ON jh1.talent_id = talents.id")
.joins("INNER JOIN job_histories as jh2 ON jh2.talent_id = talents.id")
.where("jh1.company_id = ? and jh1.end_year = ?", 1, "Present")
.where("jh2.company_id = ? and jh2.end_year != ?", 2, "Present")

Rails 5: iteration with condition to map matching records

I have this method in my rating/rating.rb Model, where I basically need to create array of arrays with matching inventory and rating IDs:
def inventory_ratings
inventory = Inventory::Inventory.where(id: inv).order(date: :desc)
rating = Rating::Rating.where(id: rtg).order(valid_from: :desc)
columns = [:inventory_id, :rating_id]
values = inventory.map {|inv|
if (inv.position_id == rating.position_id &&
rating.valid_from..rating.valid_to.include?(inv.date))
r = rating.id
end
[ inv.id, r ]
}
Rating::InventoryRating.import columns, values, validate: false
end
At the moment I get this error:
NoMethodError: undefined method "position_id" for #<Rating::Rating::ActiveRecord_Relation:0x007ffff6067bf8> since I probably have to somehow iterate through each rating to get position_id, valid_from and valid_to.
How do I add that extra iteration so each inventory record iterates through each rating record and maps if it matches IF statement, please? Thank you!
How about this:
values = []
inventory.each do |inv|
values.concat([inv.id].product(rating.where('position_id = ? AND valid_from <= ? AND valid_to >= ?', inv.position_id, inv.date, inv.date).ids))
end.uniq.compact

Ruby on Rails iterate array result into query

Following were my output from the query:
unless #slug == 'all'
#city = '' if #city.blank?
#city = #city.gsub("-", " ")
country = Country.where("lower(name) LIKE ?", "#{#city.downcase}")
gon.country = country
if country.present?
end
if #city.present?
#products = #products.where("lower(city) LIKE ? or lower(country) like ? or lower(state) LIKE ?", "%#{#city.downcase}%", "%#{#city.downcase}%","%#{#city.downcase}%")
#city_obj = City.where("lower(name) LIKE ?", "%#{#city.downcase}%").first
end
end
Here gon.country return result as:
Object
countries
:
Array[2]
0
:
"california"
1
:
"san francisco"
How can I iterate the countries and pass it to get #products result?
You can do it in two ways :
Iterate all country record and then add like query.
#product = []
country.each do |con|
#products << #products.where("lower(city) LIKE ? or lower(country) like ? or lower(state) LIKE ?", "%#{con.downcase}%", "%#{con.downcase}%","%#{con.downcase}%")
end
You can use ILIKE syntax of core sql. Which work like this :
select * from table where value ilike any (array['%foo%', '%bar%', '%baz%']);
# in your case
country_array_with_percent = country.map {|con| "%#{con}%" }
#products.where("name ILIKE ANY ( array[?] )", country_array_with_percent)
You can also make query chaining with or query, More details here

Rails - how to make a condition with "has_many" association?

I have these two models:
car
class Car < ActiveRecord::Base
has_many :colors
end
Color
class Color < ActiveRecord::Base
belongs_to :car
end
Every car can have many colors.
What I am trying to do - to fetch all cars that don't have black colors.
Is there any way to do it in one query with using ActiveRecord? I can do it this way:
#cars = Car.where('brand = ?', params[:car])
#cars.each do |car|
car.colors.each do |color|
...test that the color is not black...
end
end
But this method is a bit slow... is there a faster approach of doing this with one query?
Thank you
We don't know the attributes on your Color model, but something like this is what you're looking for:
#cars = Car.where('brand = ?', params[:car])
#cars = #cars.joins(:colors)
.where("colors.name <> 'black'").each do |car|
Or, if you're using Rails 4:
#cars = Car.where('brand = ?', params[:car])
#cars = #cars.joins(:colors)
.where.not(colors: { name: :black }).each do |car|
Try this
Car.joins(:colors).where("colors.name <> 'black'").group('cars.id')
UPDATE:
You can probably do this in two queries
black_car_ids = Color.where(:name => 'black').pluck('DISTINCT car_id')
#cars = Car.where("id not in (?)", black_car_ids).where('brand = ?', params[:car])

Wrapping 'next' and 'previous' functions

In my Rails 4 app, I defined functions in my model than get the (nth) next or previous row in de database, wrapping around the entire database, so that Item.last.next will refer to Item.first:
def next(n=0)
following = Item.where("id > ?", self.id).order("id asc") + Item.where("id < ?", self.id).order("id asc")
following[n % following.length]
end
def prev(n=0)
n = n % Item.count-1
previous = Item.where("id < ?", self.id).order("id desc") + Item.where("id > ?", self.id).order("id desc")
previous[n % previous.length]
end
This results in three database queries per method call, and I've learned to keep database queries to a minimum, so I wonder if there is a way do get this result with only one query.
What you are looking for seems a bit high level. So let's prepare the basic API at first.
def next(n=1)
self.class.where('id > ?', id).limit(n).order('id ASC')
end
def previous(n=1)
self.class.where('id > ?', id).limit(n).order('id DESC')
end
Then higher level methods
def next_recycle(n=1)
klass = self.class
return klass.first if (n = 1 && self == klass.last)
next(n)
end
def previous_recycle(n=1)
klass = self.class
return klass.last if (n == 1 && self == klass.first)
previous(n)
end
You can pick methods according to needs.

Resources