How to access data from a scope with a join - ruby-on-rails

I have two tables, People and Vehicles. I created a scope that shows which people do not have Vehicles.
scope :person_has_no_vehicle, -> { joins("LEFT OUTER JOIN vehicles ON vehicles.person_id = people.id").where('Vehicles.person_id IS NULL').limit(100)}
In my View I have
<% #person.person_has_no_vehicle.each do |test| %>
How would I access data from my vehicles table?
I.E.
<% if test.vehicles.person_id == NIL %> <-- That does not work, but you get the general idea.

First, I suggest you some refactor:
# FROM
scope :person_has_no_vehicle, -> { joins("LEFT OUTER JOIN vehicles ON vehicles.person_id = people.id").where('Vehicles.person_id IS NULL').limit(100)}
# TO
scope :without_vehicle, -> { includes(:vehicles).where(vehicles: { id: nil }).limit(100) }
Then in your view you can use:
<% People.without_vehicle.each do |people| %>
# These People instances do not have any vehicle record associated
# so `people.vehicles` in this loop should return an empty ActiveRecord::Relation
I don't understand why you "would access data from the vehicles table", the scope you want is the exact opposite: People without any vehicle associated.
Anyway, if you really want to test if there is not Vehicle corresponding to the People record, try this:
<% People.without_vehicle.each do |people| %>
<%= "THIS GUY OWNS AT LEAST 1 VEHICLE!!" if people.vehicles.present? %>

Related

Print what is not in a join table

I have a join table created from 2 other tables.
Say one model is called cat, another is called request, and the join table is called catrequest (this table would have cat_id and request_id)
How would I print all of the cats not intersected in the join table i.e. all of the cats NOT requested using rails. I saw some DB based answers, but I am looking for a rails solution using ruby code.
I get how to print a cat that belongs to a request i.e.:
<% #requests.each do |request| %>
<% request.cats.each do |cat| %>
<%= cat.name %>
<% end %>
but I don't understand how to do the reverse of this.
To get a list of cats that have never been requested you'd go with:
Cat.includes(:cat_requests).where(cat_requests: { id: nil })
# or, if `cat_requests` table does not have primary key (id):
Cat.includes(:cat_requests).where(cat_requests: { cat_id: nil })
The above assumes you have the corresponding association:
class Cat
has_many :cat_requests
end
It sounds like what you need is an outer join, and then to thin out the cats rows that don't have corresponding data for the requests? If that's the case, you might consider using Arel. It supports an outer join and can probably be used to get what you're looking for. Here is a link to a guide that has a lot of helpful information on Arel:
http://jpospisil.com/2014/06/16/the-definitive-guide-to-arel-the-sql-manager-for-ruby.html
Search the page for "The More the Merrier" section which is where joins are discussed.

How to get an ordered list of categories by names from IDs

I loaded data from database, like:
#cars = Car.where(...).order('created_at DESC').group_by { |r| r.voted.to_date }
Then, in a view, I do:
- #cars.each do |key, arr|
- categories = arr.uniq{|x| x.category_id} # so here I should get list of objects with uniuque category ID
...
- arr.each do |car|
... print information about car ...
How can I order this list of categories by their name? In the car model, there's the attribute category_id.
In order use category.name ASC. Try,
#cars = Car.includes(:category).where(...).order('category.name ASC')
"How can I order this list of categories by their name?" This brings to mind to use scopes.
Here's the setup: In category.rb
scope :by_name, -> { order(name: :asc) }
Now you have a reference handle to grab category.name sorted ascending.
So, in your view, you could do something like this for each |car|
<% if car.categories.by_name.any? %>
<%= render category_name_partial", collection:...%>
etc.
I used '.any?' only to illustrate that you have a sorted collection as a result of this action, your listed sorted by name based on scope defined by "by_name".

Rails join and HABTM relationship

In my application I have model Car which:
has_and_belongs_to_many :locations
Now I'm buidling searching and I want search Car which has given locations.
In my view I have:
.row
= horizontal_simple_form_for :cars, {url: cars_path, method: :get} do |f|
= f.input :handover_location, label: I18n.t('.handover'), collection: Location.all.map{|hl| [hl.location_address, hl.id]}
= f.input :return_location, label: I18n.t('.return') ,collection: Location.all.map{|rl| [rl.location_address, rl.id]}
= f.submit class: 'btn btn-success'
and in my controller I filter results based on params:
#cars = Car.joins(:locations).where("locations.id= ? AND locations.id= ?", params[:cars][:handover_location], params[:cars][:return_location])
But this code does not work properly. Maybe I shouldn't use "locations.id" twice?
I'm going to assume your join table is called cars_locations. If you wanted to do this just in sql, you could join this table to itself
... cars_locations cl1 join cars_locations cl2 on e1.car_id = e2.car_id ...
... which would make a pseudo-table for the duration of the query with this structure:
cl1.id | cl1.car_id | cl1.location_id | cl2.id | cl2.car_id | cl2.location_id
then query this for the required location_id - this will give you entries that have the same car at both locations - let's say the ids of the pickup and return locations are 123 and 456:
select distinct(cl1.car_id) from cars_locations cl1 join cars_locations cl2 on cl1.car_id = cl2.car_id where (c11.location_id = 123 and cl2.location_id = 456) or (cl1.location_id = 123 and cl2.location_id = 456);
Now we know the sql, you can wrap it into a method of the Car class
#in the Car class
def self.cars_at_both_locations(location1, location2)
self.find_by_sql("select * from cars where id in (select distinct(cl1.car_id) from cars_locations cl1 join cars_locations cl2 on cl1.car_id = cl2.car_id where (c11.location_id = #{location1.id} and cl2.location_id = #{location2.id}) or (cl1.location_id = #{location2.id} and cl2.location_id = #{location1.id}))")
end
This isn't the most efficient method, as joins on big tables start to get very slow.
A quicker method would be
def self.cars_at_both_locations(location1, location2)
self.find(location1.car_ids & location2.car_ids)
end
in this case we use & which is the "set intersection" operator (not to be confused with &&): ie it will return only values that are in both the arrays on either side of it.
You definitely shouldn't be using the locations.id twice in the where clause, as that is physically impossible. The resulting query from that will essentially try and find the location where it's id is both the handover location, AND the return location. So in essence, what you're asking for is something like
where 1 == 1 AND 1 == 2
Which needless to say, will always return nothing.
In theory if you just change the AND for an OR you'll get what you're after. This way, you'll be asking the database for any location that has an ID or either start_location OR handover_location
UPDATE
Re-read the question. It's a little tricker than I'd thought initially, so you'll probably need to do some processing on the results. As I've said, using the AND query the way you are is asking the database for something impossible, but using the OR as I originally said, will result in cars that have EITHER or the locations, not both. This could be done in raw SQL, but using Rails this is both awkward, and frowned upon, so here's another solution.
Query the data using the OR selector I originally proposed as this will reduce the data set considerably. Then manually go through it, and reject anything that doesn't have both locations:
locations = [params[:cars][:handover_location], params[:cars][:return_location]]
#cars = Car.joins(:locations).where("locations.id IN [?]")
#cars = #cars.reject { |c| !(locations - c.location_ids).empty? }
So what this does, is query all cars that have either of the requested locations. Then it loops through those cars, and rejects any whose list of location id's does not contain both of the supplied IDS. Now the remaining cars are available at both locations :)

Optimization of calculating recommendations and work with many-to-many relationship

I have two models User and Brand, and Many-to-Many relationship between them (through UserBrand table). I have about a thousand users, a thousand brands and a hundred favorite brands of each user.
User.all.count # => 1000
Brand.all.count # => 1000
User.find(1).brands # => 100
If I'd like to find 5 users, which favorite brands almost equal to current users', I wrote the following in the User model
class User < ActiveRecord::Base
has_many :user_brands
has_many :brands, :through => :user_brands
def similar_users
result = {}
User.all.each do |u|
result[u] = shared_brands_with u.brands
end
result.sort{ |a, b| b[1] <=> a[1] }[1..5].map!{ |e| e[0] }
end
def shared_brands_with(brands)
(brands & #brands).size
end
end
and the following in the users/show view
<h2>Similar users</h2>
<ul>
<% #user.similar_users.each do |user| %>
<li><%= link_to user.name, user %></li>
<% end %>
</ul>
But it takes about 30-60 seconds to see user recommendations in the browser.
So my question is "How can I speed up calculation of recommendations?"
UPD: using
User.includes(:brands).each do |u|
result[u] = shared_brands_with u.brands
end
doubles performance, but even with 50 brands instead of 100, giving recommendation in 10 sec is very slow.
So, basically. You're pulling the whole users table. And for each, grabbing the brands list for that user, and presumably the brands themselves. And filtering. I wouldn't exect it to be fast. :-)
You're going to need to rewrite that logic in SQL to make it fast. Sorry I'm not more fluent than at in ruby - can't really make sense of the criteria... But in essence, fetch the brands through a single query.
It'll be big and ugly, full of joins and in() and possibly group by/having clauses, but it'll be faster than your current approach.

Rails JOIN TABLES

I have a following SQL QUERY:
SELECT articles.name, articles.price, users.zipcode FROM articles INNER JOIN users ON users.id = articles.user_id WHERE vectors ## to_tsquery('crime') ORDER BY articles.price ASC
And I Would like to write it inside of a find method from an ActiveRecord Class named Articles (Articles belongs_to user). Basically i wanna search for Articles and access the zipcode propertie from the User (user has_many Articles)
I wrote the following version, but i'm not sure that its working because in the response i dont receive any information about the user zipcode.
a = Article.find(:all,:conditions=>"vectors ## to_tsquery('crime')",:joins= >:user,:order=>:price,:include=>:user)
But i have no idea how to access the zipcode information. how can I access this information ? this is the right approach ?
Regards,
Victor
If you've coupled Articles and Users like you say above, this should be pretty easy:
#articles = Article.find(:all, :conditions => "…", :include => :user)
Then, in your view, you can do:
<ul>
<% for each article in #articles do %>
<li><%= article.user.zipcode %></li>
</ul>
<% end %>
This works because Rails creates a property for the parent object (User) in the model (Article) - you can read more about that in the API docs. This even works without the "include" key above, but leaving it out would mean a database query in every step of the loop.

Resources