How to search through nested has_many through? - ruby-on-rails

Here what I have now :
class Pokemon < ActiveRecord::Base
has_many :pokemon_moves, dependent: :destroy
has_many :moves, through: :pokemon_moves
end
class PokemonMove < ActiveRecord::Base
belongs_to :pokemon
belongs_to :move
end
class Move < ActiveRecord::Base
belongs_to :type
end
And many others, no important.
I just would like to search for a Pokémon which could have Move named "pound" and another Move "mega-punch"
I tried :
Pokemon.joins(:moves).where(moves: {name: 'pound'}).where(moves: {name: 'mega-punch'})
But no result. The translated SQL is :
SELECT "pokemons".* FROM "pokemons" INNER JOIN "pokemon_moves" ON "pokemon_moves"."pokemon_id" = "pokemons"."id" INNER JOIN "moves" ON "moves"."id" = "pokemon_moves"."move_id" WHERE "moves"."name" = $1 AND "moves"."name" = $2 [["name", "pound"], ["name", "mega-punch"]]
If I only search for one move, it works just fine, but I can't get it with two moves.
I have tried many things, but all concludes to bad results.
Of course, I have a Pokémon that have those moves, if I do Pokemon.find_by_name('golurk').moves I can retrieve those two moves.
Thanks !
UPDATE 1 :
I made it work by simply using & operator :
Pokemon.joins(:moves).where(moves: {name: 'pound'}) & Pokemon.joins(:moves).where(moves: {name: 'mega-punch'})
But it's really not efficient, and I'm pretty sure we can find a better way.

The reason you get no results is because you are searching for moves with the name pound AND the name mega punch.
Change your code to:
Pokemon.joins(:moves).where(moves: {name: ['pound', 'mega-punch']})
Chaining where results in AND in your sql. If you want OR, either use an array like in the example above, or write your own sql.

Related

ActiveRecord get all model records plus those matching some criteria

I'm trying to create a set of checkbox tags in order the user can see how much of them he has (has subscribed to), and to which no.
My models are User, Category and Preferences which handles the has_many through relationship between both first models:
class User < ApplicationRecord
has_many :preferences
has_many :categories, through: :preferences
end
class Category < ApplicationRecord
has_many :preferences
has_many :users, through: :preferences
end
class Preference < ApplicationRecord
belongs_to :user
belongs_to :category
end
The goal is to get all the categories existent and to be able a extra way to check if the user has such categories, this way I can use an active CSS rule and show them different to those which the user doesn't have.
I've tried just getting all the categories name, and also all the categories name for those corresponding to the user and joining them as one array with multiple 2 elements arrays inside and I get something like:
[
["coffee", "coffee"],
["coworking", "food"],
["event", "pub"],
["food", "restaurant"],
["landmark", "shop"],
["nightlife", "vacation"],
["pub", nil],
["restaurant", nil],
["shop", nil],
["transportation", nil],
["vacation", nil]
]
This way would be good if I can make them match in name, since the names won't never be repeated for each array, maybe like:
[
["coffee", "coffee"],
["coworking", nil],
["event", nil],
["food", "food"],
["landmark", nil],
["nightlife", nil],
["pub", "pub"],
["restaurant", "restaurant"],
["shop", "shop"],
["transportation", nil],
["vacation", "vacation"],
]
So I can see which of those are nil in the last (second) element in the array and don't print the CSS rule for checked elements.
But it seems I need to do it outside an ActiveRecord query.
Does it exist any way to get the desired result by using ActiveRecord, or do I have to handle it manually, or to change the schema?
You can do it in a few steps.
First get all categories
all_categories = Category.all
Then get the user categories (I'm assumiming you already have code to define current_user for the request)
#user_categories = current_user.categories
Then you can find the difference between the two:
#non_user_categories = all_categories - #user_categories
The #user_categories and all_categories are not, strictly speaking, Arrays - they are ActiveRecord::Relation objects. But you can still use array subtraction to find the difference. Note that #non_user_categories is an actual Array.
You can do it another way which avoids loading excess records, though for a small data set the performance gain will be inconsequential.
#user_categories = current_user.categories
#non_user_categories = Category.joins(:preferences).where.not(
"preferences.user_id = ?",
current_user.id
)

Rails eager loading doesn't work(still n+1)

I have included client_person and case_histories but when querying case_histories it still generates n+1 query.
I have tried for a long while but nothing seems to work.
#client_records = #records.includes({person: [:client_person, :case_histories]})
In person model:
belongs_to :client_person, class_name: 'Client::Person'
has_many :case_histories, through: :region_profiles, class_name: 'Admin::PeopleCaseHistory'
the query that generates n+1:
#client_records.each do |record|
record.person.case_histories.select{ |hist| hist.case_type == 2}.first
end
Specifically, after correctly loading the case_histories for all clients in the list(resulting from the includes clause), it still one by one queries for the case_histories of each client.
The first query generated by includes:
Admin::PeopleCaseHistory Load (46.9ms) SELECT "case_histories".* FROM "case_histories" WHERE "case_histories"."region_profile_id" IN (411, 16804, 572, 19506, 16539, 692, 4828)
The subsequent N+1 queries:
Admin::PeopleCaseHistory Load (29.5ms) SELECT "case_histories".* FROM "case_histories" INNER JOIN "region_profiles" ON "case_histories"."region_profile_id" = "region_profiles"."id" WHERE "region_profiles"."person_id" = $1 [["person_id", 9867]]
Admin::PeopleCaseHistory Load (34.3ms) SELECT "case_histories".* FROM "case_histories" INNER JOIN "region_profiles" ON "case_histories"."region_profile_id" = "region_profiles"."id" WHERE "region_profiles"."person_id" = $1 [["person_id", 430]]
Please advise!
Thanks
Looks like you have both people_region_profile_id and region_profile_id on your case_histories table. One of those is getting used during eager-loading, but the other is not.
It would help for you to list out your entire model/relation graph because I'm unable to construct what exactly is going on. It seems to me that your graph looks something like this:
class Person < ActiveRecord::Base
belongs_to :client_person
has_many :region_profiles
has_many :case_histories, through: :region_profiles
end
class RegionProfile < ActiveRecord::Base
belongs_to :person
has_many :case_histories
end
class CaseHistory < ActiveRecord::Base
belongs_to :region_profile
end
But clearly I'm missing something if case_histories have two different foreign keys.

Neo4j Cypher: How to fetch nodes with conditional query on reation

I have two Neo4j Nodes and one relation:
class StayPal
include Neo4j::ActiveNode
has_many :in, :places, origin: :owner
has_many :in, :shared_places, rel_class: 'HouseMate'
end
class Place
include Neo4j::ActiveNode
has_one :out, :owner, type: :owner_of, model_class: 'StayPal'
has_many :out, :house_mates, rel_class: 'HouseMate'
end
class HouseMate
include Neo4j::ActiveRel
include Enumable
creates_unique
from_class 'Place'
to_class 'StayPal'
type 'shared_with'
property :status, default: 0
enum_attr status: [:pending, :approved, :declined, :cancelled]
end
Objective: My objective is get places & shared_places of staypal together but the shared places included if they are status == approved
Query:
Neo4j::Session.current.query
.match(n: { StayPal: { user_id: 1 } })
.match('n<-[rel1:`owner_of`]-(result_places:`Place`)')
.match('n<-[rel2:`shared_with`]-(result_places1:`Place`)')
.pluck(:result_places1, :result_places)
With this I am getting the places and shared places of staypal
But I want shared places where status = 1
Modified Query
Neo4j::Session.current.query
.match(n: { StayPal: { user_id: 1 } })
.match('n<-[rel1:`owner_of`]-(result_places:`Place`)')
.match('n<-[rel2:`shared_with`]-result_places1:`Place`)')
.where('result_places1.status = 1')
.pluck(:result_places1, :result_places)
But with this I am getting no records
Some other helping queries
Neo4j::Session.current.query
.match(n: { StayPal: { user_id: 1 } })
.match('n<-[rel1:`owner_of`]-(result_places:`Place`)')
.match('n<-[rel2:`shared_with`]-result_places1:`Place`)')
.where('result_places1.status = 1')
.pluck(:result_places1)
Output:
[CypherRelationship 1239]
You are using the neo4j-core Query API, which you can do and which should allow you to get ActiveNode and ActiveRel objects, but there is a higher level API which is easier:
StayPal.where(user_id: 1).places(:place1).shared_places(:places2).pluck(:place1, place2)
To do this I assumed that you add this association to Place:
has_many :both, :shared_places, type: :shared_with
Note that I used the singular form for variables. It's important to remember that when you are matching that it does one match at a time. Singular variables help us to keep that in context.
Aside from that, though, I think you have a deeper issue that your HouseMate relationship is going from a Place to a StayPal. What is your model? If you want to record two people staying in the same house, you might want to have a new node with a label like HouseMateGroup and that node could point to the Place as well as two (or more) StayPals.
EDIT:
I think I'm understanding your use case more. I would probably make the model (:StayPal)-[:LIVES_IN]->(:Place)<-[:LIVES_IN]-(:StayPal)
Any given step in that doesn't map to the idea of a "house mate", but you can easily get the housemates by following relationships/associations. So if you wanted to get housemates you might do:
pal = StayPal.find_by(user_id: 1)
pal.places.people
That would get you all of the people that are in the places which user_id: 1 is in.
If you wanted to find all places which have associated people:
Place.as(:place1).people.places(:place2).pluck(:place1, :place2)
You could even count the number of people that exist in that relationship between places:
Place.as(:place1).people(:person).places(:place2).pluck(:place1, :place2, 'count(person)')

rails: query and filter n:m related objects using active record

I'm trying to use active-record query possible connections between airports.
I described the models I created already in another question here:
n:m self-join with ruby on rails active record
Basically, what I can do now is that:
ny = Airport.create({"city" => "New York"})
la = Airport.create({"city" => "Los Angeles"})
ny.destinations << la
la.destinations << ny
I ran into an issue querying the data I'm looking for, which is quite simple in SQL but I had no luck with active record yet.
ny = Airport.where('city = ?', 'New York')
ny.destinations
returns the correct objects, but all of them.
The SQL query looks like that:
SELECT "airports".* FROM "airports" INNER JOIN "connections" ON "airports"."id" = "connections"."destination_id" WHERE "connections"."airport_id" = 3
I'd like to filter those results by cities starting with "s" for example, so an SQL query could look like that:
SELECT "airports".* FROM "airports" INNER JOIN "connections" ON "airports"."id" = "connections"."destination_id" WHERE "connections"."airport_id" = 3 AND airports"."city" LIKE "s%"
I tried it this way:
ny.destinations.where('city LIKE ?', '#{params[:query]}%')
But I always get an empty result.
How could I use active record to filter my resulting objetcs?
edit: Thats the best solution I found so far:
I added the cityLike() method to the Airport model:
app/models/airport.rb:
class Airport < ActiveRecord::Base
attr_accessible :city, :name
has_many :connections
has_many :destinations, :through => :connections
has_many :inverse_connections, :class_name => "Connection", :foreign_key => "destination_id"
has_many :inverse_destinations, :through => :inverse_connections, :source => :airport
def self.cityLike(query)
where("city LIKE ?", "%#{query}%")
end
end
app/model/connection.rb:
class Connection < ActiveRecord::Base
attr_accessible :destination_id, :airport_id
belongs_to :airport
belongs_to :destination, :class_name => "Airport"
end
Now I can query the objects with the following statement:
Airport.find(1).destinations.cityLike("a")
Not sure if it's the best solution, but it produces the query I was looking for.
Thanks a lot to all af you!
ActiveRecord::Base.execute(sql) lets you use pure SQL to do your query and returns the relevant model.
What about this?
Airport.find(:all, joins: "INNER JOIN `connections` ON airports.id = connections.destination_id").where("connections.airport_id = ? AND airports.city LIKE ?", ny_id, "s%")
This code:
ny.destinations.where('city LIKE ?', '#{params[:query]}%')
works like this--first you have an object ny repesenting the city of New York. When you say ".destinations" you have now followed a relation you defined in your model to retrieve all the destinations that you can get to from New York. However, if I'm imagining your database schema correctly, these destinations don't actually have a field called "city"; instead, they have a destination_id, which ties the destination to a particular airport, and it's the airport that has a city associated with it.
So when you query the destination table for 'city LIKE ?', it doesn't find any matching records.
Instead, try
ny.destinations.joins(:airports).where('city LIKE ?', '#{params[:query]}%')

Rails Equivalent of SQL

Given the following models:
class PeriodBilling < ActiveRecord::Base
belongs_to :appcode
belongs_to :period
belongs_to :sla
belongs_to :unit_type
belongs_to :dpc_employee
belongs_to :general_ledger
end
class GeneralLedger < ActiveRecord::Base
has_many :appcodes
has_many :sla_task_details
belongs_to :expense_category
has_many :period_billings
has_many :expected_billings
end
How would I find the following SQL equivalent in Rails, to display as a matrix with periods down the left side and general_ledgers across the top, with the sum(pb.current_amt) in the appropriate fields?
select pb.pe_number, gl.general_ledger_number, sum(pb.current_amt)
from period_billings pb, general_ledgers gl
where pb.sla_id = 21
and pb.general_ledger_id = gl.id
group by pb.pe_number, gl.general_ledger_number
In Active Record terms, this is kinda what I'm trying to find:
#sla = Sla.find(params[:id])
#period_billings = PeriodBilling.where("sla_id = ?", #sla.id).group_by(&:general_ledger_id)
#billing_sum = #period_billings.inject(0){|sum,billing| sum+billing.current_amt}
So, I want to find all period_billings for the selected sla, grouped by general_ledger_id to get the sum of the period_billing.current_amt for those records. Then, I want to put the 12 periods down the side to show the summed amount in its appropriate general_ledger column across the top.
Thanks in advance!!! :)
Just a wild little stab at what I suggest you do. Some things are better meant for direct sql, and when that arises, like you're requesting, it's best to just use sql.
ActiveRecord::Base.connection.execute <<-EOF
select pb.pe_number, gl.general_ledger_number, sum(pb.current_amt)
from period_billings pb, general_ledgers gl
where pb.sla_id = 21
and pb.general_ledger_id = gl.id
group by pb.pe_number, gl.general_ledger_number
EOF

Resources