How to complete a query in Rails with a deep condition? - sql

I am attempting to query a database based on a deep condition. As an example, I would like to find all of the people whose primary pet has a specific breed name:
Person > Pet > Breed > Name
The models might look like this.
class Person
has_many pets
attr_accessor :primary_pet
end
class Pet
belongs_to Person
has_one Breed
end
class Breed
# no belonging relationship
attr_accessor :name
end
Now I could do:
Person.find_each do |person|
puts person if primary_pet.breed.name == 'Norwich Terrier'
end
but I want to figure out the proper query syntax for doing this with a where method. The following does not work:
Person.where(primary_pet.breed.name: 'Norwich Terrier').find_each do |person|
puts person
end

I formalized the model relationships with minor edit, and ended up with this:
class Person < ActiveRecord::Base
has_many :pets
attr_accessor :primary_pet
end
class Pet < ActiveRecord::Base
belongs_to :person
belongs_to :breed
end
class Breed < ActiveRecord::Base
# no belonging relationship
attr_accessor :name
end
The best query that I came up with (that actually worked) was this:
Person.joins(pets: :breed).where(breeds: { name: 'Norwich Terrier' }).where("people.primary_pet = pets.id")
This says to join the relationship for pets and to breeds. This allows using the breeds structure to filter for 'Norwich Terrier'. And to finish it off, to additionally filter by primary_pet_id related to pets.id.
That should get you what you asked for in the question.
Here's how I tested it:
breed_names = ["Beagle", "Collie", "Norwich Terrier", "German Shepard", "Dachshund"]
people_names = ["Barbara", "Connie", "Paula", "Gerald", "Dave"]
# Add breeds
breed_names.each {|name| Breed.create!(name: name) }
# Add people
people_names.each {|name| Person.create!(name: name) }
# Add some pets
people_names.each do |name|
person = Person.find_by(name: name)
breed_names.sample(3).each do |breed|
breed = Breed.find_by(name: breed_name)
Pet.create!(breed_id: breed.id, person_id: person.id)
end
end
# Assign the primary_pet for each person
Person.all.each {|person| pet = person.pets.first ; person.update!(primary_pet: pet) if pet }
# List all people and their pets by breed
Person.all.each do |person|
puts "#{person.name}: #{person.pets.map {|pet| pet.breed.name}}"
end
# List all people and their primary pet breed
Person.all.each do |person|
puts "#{person.name}: #{person.primary_pet ? person.primary_pet.breed.name : ''}"
end
# Run the query
Person.joins(pets: :breed).where(breeds: { name: 'Norwich Terrier' }).where("people.primary_pet = pets.id")

Related

Rails admin gem, polymorphic association

How to get the queried data having polymorphic association
I have an 3 models
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end```
Employee and Product have column **is_active**.
In rails admin dropdown. I want to display the data where employee and product is **is_active = true.**
Have tried multiple ways to achieve this.
Please let me know if there is any solution?
You can write a custom scope in picture model as below
scope :list_active(imageable_type), -> {
where(imageable_type: imageable_type)
.joins("INNER JOIN #{imageable_type.pluralize} ON
{#imageable_type.pluralize}.id = imageable_id AND
imageable_type = '#{imageable_type}'")
.where('#{imageable_type.pluralize}.is_active = ?', true)
}
Then you can simply list and use the response.
E.g result = []
result << Image.list_active('Employee')
result << Image.list_active('Product')

How do I write an SQL query for a has_many through relationship where a foreign_key_id is "1" or NULL in Rails?

I have project where you own a fantasy team (Roster model) and you pick teams (SportsTeam model), similar to a college basketball pool. These two models are joined by a "has many through" relationship on a RosterSpots model. I have different leagues (FantasyLeague model) identified on my Roster model by fantasy_league_id.
I'm trying to set up a view that takes parameter[:fantasy_league_id]. The view will show all of the sports_teams (whether they are owned in that fantasy_league or not) and which sports_teams are currently on a roster in that fantasy_league.
My current code will show all of the teams owned in a fantasy_league, i.e. fantasy_league_id = 1, and all sports_teams where fantasy_league_id is NULL, but I can't figure out how to show a team that is not owned in fantasy_league_id = 1 but is owned in fantasy_league_id = 2. I need another OR but I can't figure out how to write it.
Here is short example:
team (fantasy_league_id):
Seattle (NULL),
Atlanta (1),
Atlanta (2),
Arizona (1),
New York (2)
With params[:fantasy_league_id] = 1, I correctly show Seattle, Atlanta, and Arizona but I need to show New York as not being on a roster in that league.
Here are my models:
class SportsTeam < ActiveRecord::Base
has_many :roster_spots
has_many :rosters, through: :roster_spots
belongs_to :sports_league
scope :left_joins_rosters, -> { joins("LEFT OUTER JOIN roster_spots
ON roster_spots.sports_team_id = sports_teams.id
LEFT OUTER JOIN rosters ON rosters.id = roster_spots.roster_id" ) }
scope :select_rosters, -> { select("sports_teams.*, rosters.*") }
scope :joins_sports_league, -> { joins(:sports_league)
.select("sports_teams.*, sports_leagues.*") }
class RosterSpot < ActiveRecord::Base
belongs_to :sports_team
belongs_to :roster
class Roster < ActiveRecord::Base
has_many :roster_spots, dependent: :destroy
has_many :sports_teams, through: :roster_spots
belongs_to :fantasy_league
class FantasyLeague < ActiveRecord::Base
has_many :rosters
has_many :sports_teams, through: :rosters
Here is my controller:
class StandingsController < ApplicationController
def list_teams
#sports_teams = SportsTeam.left_joins_rosters.joins_sports_league
.select_rosters
.where("fantasy_league_id is NULL OR fantasy_league_id = ?",
params_fantasy_league)
end
private
def params_fantasy_league
params_fantasy_league = params[:fantasy_league_id] || 1
end
end
I tried writing the WHERE statement directly in the left_joins_rosters scope but got the same result.
scope :left_joins_rosters_league, -> { joins("LEFT OUTER JOIN roster_spots
ON roster_spots.sports_team_id = sports_teams.id
LEFT OUTER JOIN rosters ON rosters.id = roster_spots.roster_id
WHERE fantasy_league_id is NULL OR fantasy_league_id = 1") }
I also considered starting with the FantasyLeague model with a .where(fantasy_league_id: params[:fantasy_league_id], but I think I would need to use a RIGHT OUTER JOIN to get all of the sports teams which isn't supported in SQLite.
Thanks!
Axel

Many to many relationship with ability to set a state (active)

I have a fully working (for some time now) many-to-many relationship in my Rails application.
Instructors has many Schools (through SchoolsToInstructorsAssociations)
Schools has many Instructors (through SchoolsToInstructorsAssociations)
At this time, I would like the ability to have an "active state" in addition to simply adding or removing an Instructor from a School or a School from an Instructor.
I want an Instructor to be set as inactive before being removed completely at a later point (or reactivated).
My first thought was to add an 'active' boolean to the relationship model (SchoolsToInstructorsAssociations), but there's no simple way to access this attribute to update or query it).
My second thought was to simply create another relationship model with the 'active' attribute, but it's redundant and something extra I have to track.
Maybe a custom many-to-many module? Create a SchoolsToInstructorsAssociations controller?
class Instructor < ActiveRecord::Base
has_many :schools_to_instructors_association
has_many :schools, :through => :schools_to_instructors_association
end
class School < ActiveRecord::Base
has_many :schools_to_instructors_association
has_many :instructors, :through => :schools_to_instructors_association
end
class SchoolsToInstructorsAssociation < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
I also plan to create a history record each time an instructors 'active' state changes or an instructor is removed or added to a school. Not asking how to do this, but wondering if it could be used to track an instructors 'active' state.
class SchoolsController < ApplicationController
def instructors_index
#school = School.find(params[:id])
instructors = find_instructors
#active_instructors = instructors[0]
#inactive_instructors = instructors[1]
respond_to do |format|
format.html # index.html.erb
format.json { render json: #schools }
end
end
private
def find_instructors
active = []; inactive = []
#school.instructors.each do |s|
if SchoolsToInstructorsAssociationRecord.where(user_id: s, school_id: #school)[0].active?
active << s
else
inactive << s
end
return [active, inactive]
end
end
end
class SchoolsToInstructorsAssociationRecord < ActiveRecord::Base
default_scope order('created_at DESC')
attr_accessor :user_id, :school_id, schools_to_instructors_association_id, :active
end
Sounds like you can accomplish what you're trying to do with scopes. Add a boolean column for 'active' as you described for the 'Instructor' class, then you can add scopes for it:
class Instructor < ActiveRecord::Base
...
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
...
end
Then for a given School, you can get the active (or inactive) instructors for that school:
#school.instructors.active
=> SELECT "instructors".* FROM "instructors" WHERE "instructors"."school_id" = <id> AND "instructors"."active" = 't'
If you wanted to do some operations on all the inactive instructors (like destroy them, as an example), you could do:
Instructor.inactive.map(&:destroy)
And you can of course write whatever custom methods you want for the Instructor or School classes.

Select distinct including a value from a different model/table

I have 2 models/tables:
class CollectionPoint < ActiveRecord::Base
belongs_to :collection_type
...
class CollectionType < ActiveRecord::Base
has_many :collection_points
...
Every CollectionPoint has a city (column)
Every CollectionType has a name (column)
I would like to get all the distinct collection type names in each city in order to show a directory like this in my view:
city A
collection_type.name A
collection_type.name B
collection_type.name C
...
city B
collection_type.name A
collection_type.name B
collection_type.name C
...
city C
collection_type.name A
collection_type.name B
collection_type.name C
...
...
The best thing would be an array that is grouped by cities so that i could go like this in my view:
#cities.each do |city|
...
city.each do |collection_type_name|
...
end
end
So i tried this:
CollectionPoint.select("DISTINCT city, collection_type_id")
But then i get only the collection type ids, not the collection type names. Also i don't need the collection point ids (which are nil in the query result anyway), so i tried this:
CollectionPoint.includes(:collection_type).select("DISTINCT city, collection_types.name").references(:collection_types)
But this is not getting me anywhere neither.
I'm quite frustrated because i know there must be a solution to this i don't have a clue about. Maybe you could help me?
Cheers
Solution A
The simplest solution to this problem was adding this line of code to my controller:
#cities = CollectionPoint.includes(:collection_type).select("DISTINCT city, collection_type_id").order(:city).group_by(&:city)
So i could show the results in my view accordingly:
- #cities.each do |city, collection_points|
%h2
= city
= content_tag :ul, title: "... " + city do
%li
= link_to "... " + city, "/.../" + city
- collection_points.each do |cp|
%li
= link_to cp.collection_type.name + " in " + city, "/" + cp.collection_type.name + "/" + city
Solution B
I guess the best solution is to create a separate City model and rework your assosiations:
class City < ActiveRecord::Base
has_many :collection_points
has_many :collection_types, through: :collection_points
...
class CollectionPoint < ActiveRecord::Base
belongs_to :collection_type
belongs_to :city
...
class CollectionType < ActiveRecord::Base
has_many :collection_points
has_many :cities, through: :collection_points
...
Then you can do all kinds of stuff, for example finding all collection types in a specific city:
City.where(name: "Cologne").first.collection_types
To list all distinct collection types in all distinct cities, the controller action looks like this:
#cities = City.includes(:collection_types).distinct
And in the view you can go like:
#cities.each do |city|
city.name
city.collection_types.each do |collection_type|
collection_type.name
end
end
More information on this can be found here:
Rails Model Assosiations
Thanks for your help, guys!
Do your CollectionName and CollectionType models have and belongs to many records? If so, this is the set up I would advise with:
Collector.rb
class Collector < ActiveRecord::Base
attr_accessible :collection_point_id, :collection_type_id
belongs_to :collection_point
belongs_to :collection_type
end
CollectionPoint.rb
class CollectionPoint < ActiveRecord::Base
attr_accessible :city
has_many :collectors
has_many :collection_types, :through => :collectors
end
CollectionType.rb
class CollectionType < ActiveRecord::Base
attr_accessible :name
has_many :collectors
has_many :collection_points, :through => :collectors
end
Then you can utilise the new ActiveRecord association and select distinct records with the group method:
#cities = CollectionPoint.select("DISTINCT(CITY)").all
Then print the #cities object with an each do method.
#cities.each do |city|
...
city.collection_types.each do |collection_type_name|
...
end
end
what about using active record -
grouped_by_city = CollectionPoint.all.group_by { |cp| cp.city }
would return
{ :city =>{stuff in here}}
and then you could do this:
- grouped_by_city.each do |city, info|
%p= city
%ul
%li= info.collection_type.name #not sure what this should be? depends on your models
if you play around with that a bit I think it will work for you. irb is your best friend!

Using ActiveRecord::Association methods where associated "child" model uses STI

I've got a super-class (model) Measurement and two sub-classes: WeightMeasurement and LengthMeasurement.
I've then got a Person class which as many WeightMeasurements and LengthMeasurements.
The issue is when creating a new measurement for a Person, I want to use a shared controller that will handle both weight and length measurements.
However, the way that I would typically build up a Person's measurements would be access them bia the parent (Person). Like person.weight_measurement.build. The problem is that I don't know what to put here... person..build ?
# Base-model, includes "type" column in database.
class Measurement < ActiveRecord::Base
belongs_to :person
end
# model subclass
class WeightMeasurement < Measurement
end
# model subclass
class LengthMeasurement < Measurement
end
class Parent < ActiveRecord::Base
has_many :weight_measurements, :dependent => :destroy
has_many :length_measurements, :dependent => :destroy
end
# Single controller for "Measurements"
class MeasurementsController < ApplicationController
...
def new
person = Person.find(params[:person_id])
#
normally would do this, but because I am using STI,
# I don't know that it is a Person's "weight" measurement we are creating
#
# #measurement = #person.weight_measurements.build
#
...
end
...
end
What I normally do, is to create a hidden field in my form, which contains the type I am trying to create.
<%= hidden_field_tag :type, "weight_measurement" %>
You could also have it as a visible form option (say a radio button or select - instead of the hidden field above)
In your controller, you can do the following then:
if ["length_measurement", "weight_measurement"].include?(params[:type])
#measurement = "Measurement::#{params[:type].classify}".constantize.new(:person => #person)
end