I've read the Rails API docs on this and have googled already, but the examples I've come across don't work. I'm sure this is a duplicate question, but none of the current ones work so I'm asking it again.
This is my model setup, 2 levels deep:
class Company < ActiveRecord::Base
has_many :offices
end
class Office < ActiveRecord::Base
belongs_to :office
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :office
end
I need to find all the companies that have associated contacts with a given user_id value.
user_id = 1
Company.includes(offices: :contacts).where("contacts.user_id = ?", user_id)
I keep getting an error: TinyTds::Error: The multi-part identifier "contacts.user_id" could not be bound.
The only table with a user_id is contacts. Unfortunately I have to use MS SQL Server as the DB, hence the reason I'm using TinyTds.
I'm assuming my Rails AR syntax is incorrect, but the API docs are rather confusing to me on multi-level queries like this.
Related
I have an object called Company which has a method called owners that returns an array of User objects. I want to filter those companies by checking if the current user is in the owner's array. first I did something like this (which works):
Company.select { |c| c.owners.include?(current_user) }
However, I figured a more efficient check would be comparing only the ids instead of whole objects:
Company.select { |c| c.owners.map(&:id)include?(current_user.id) }
Can anyone help me understand if there is a difference between the two options?
There's really no difference between the two. Both are very inefficient and won't work if your Company table is large. They load all the Company records into memory, and furthermore call c.owners which fires an additional query on each of the records. This is called an N+1 query. You can address the N+1 part by using Company.all.includes(:owners), but you'd still have the issue of loading everything into memory.
It's hard to give you exact code since you haven't shared your model definition (specifically, how the Company#owners association is defined). But I'll assume you have a CompanysOwners join table. In which case I would recommend the following code:
companies = CompanysOwners.where(owner: current_user).includes(:company).map(&:company)
This only fires off a single query, and doesn't load more records into memory than are needed. You could get rid of the includes(:company) part and it would still work, but be slower.
If your Company#owners association is defined differently than this, feel free to leave a comment and I can show you how to modify this for your needs.
By the way, to address your original question more directly ... under the hood, c.owners.include?(current_user) uses == to compare the records. And ActiveRecord::Core#== is comparing the IDs under the hood. You can see the source code here for proof of that: https://api.rubyonrails.org/v6.1.3.1/classes/ActiveRecord/Core.html#method-i-3D-3D. So they really are doing the same thing.
I have an object called Company which has a method called owners that returns an array of User objects.
Well that's your problem right there.
This should really be handled on the model layer by setting up an indirect association:
class User < ApplicationRecord
has_many :company_ownerships,
foreign_key: :owner_id,
inverse_of: :owner
has_many :companies, through: :company_ownerships
end
class CompanyOwnership < ApplicationRecord
belongs_to :owner, class_name: 'User'
belongs_to :company
end
class Company < ApplicationRecord
has_many :company_ownerships
has_many :owners, through: :company_ownerships
end
This will let you get the companies owned by a user by simply calling:
current_user.companies
This is vastly more efficient as it does a single query to fetch the associated records instead of loading the entire table into memory. This will return an ActiveRecord::Relation object and not an array which allows you to add additional scopes if needed:
current_user.companies.where(bankrupt: false)
Its also superior from a code design standpoint as the buisness logic is encapsulated in your models instead of leaking the implementation details all over the place.
It will also let you include/preload/eager_load to avoid n+1 queries:
#users = User.include(:companies)
.all
#users.each do |user|
# this loads all the companies in one single query instead
# of one query per user
user.companies.each do |company|
puts company.name
end
end
If you ever for some reason need to check if two records are related you want to use a join and where clause:
#companies = Company.joins(:owners)
.where(users: { id: current_user.id })
This is my Teams-Controller.rb
#my_teams = current_user.teams.all
#available_teams = Team.where.not(#my_teams.all)
end
I'm trying to display all teams that the user currently has not joined. I have three models Team, TeamMember, and User. The purpose is to take the user to the show method of an available team and allow them to join the team.
Team Model team.rb
class Team < ApplicationRecord
has_many :team_members
has_many :users, through: :team_members
end
User model user.rb
class User < ApplicationRecord
has_many :team_members
has_many :teams, through: :team_members
end
Team Member model
class TeamMember < ApplicationRecord
belongs_to :user
belongs_to :team
end
Try this way
#my_teams = current_user.teams
#available_teams = Team.where.not(id: #my_teams.pluck(:id))
Basically what we want to achieve is a SQL query somehow like this
SELECT *
FROM teams
WHERE id NOT IN (1, 2, 3)
I have a model: Animal
belongs_to :owner
I have a model: Owner
has_many :animals
Owner has a boolean attribute active
def self.not_active
where('owner.active == ?', false)
end
The above code does not work for some reason. I've tried lots of things around it, but keep getting errors. When calling something like Animal.first.owner.active I get back either true or false, but the database call must be different...
For clarification I want the Animals for which their owner is not active. I don't want to do the opposite call though (Starting with the owner db and checking active and returning animals) because of calls I'm linking together off of Animal
Your code should be:
def self.not_active
includes(:owner).where("owners.active = ?", false)
end
When referencing a model in pure SQL, as you are doing so above, you need to use the table name, not the singular model name. Thus, owners, not owner.
A clearer style would be:
def self.not_active
includes(:owner).where(owners: {active: false})
end
Rails AREL will take care of referencing the active attribute in the query correctly, and you don't have to worry about the exact table/model name.
I'm building a webapp that has its own database called 'products_db'. But my app will have to call reviews which is located in the database 'reviews_db', which is a legacy db being used by another system that I can't do anything because client wants it that way.
So, luckily both db are located in the same SQL Server (MSSQL). I've already have te 'activerecord-sqlserver-adapter' working but I need to figure out a way to access 'reviews_db' from my webapp.
The reviews_db doesn't follow any Rails convention because its a legacy system.
So, my class Product:
class Product < ActiveRecord::Base
attr_accessible :name, :description, :price
has_many :reviews
end
And my class Review:
class Review < ActiveRecord::Base
# THIS CLASS DOESN'T FOLLOW RAILS CONVENTION
# HOW DO I SET AND MANAGE LEGACY PRIMARY KEY?
# HOW DO I CONNECT THIS MODEL TO THE OTHER DATABASE?
# HOW DO I CONNECT THIS MODEL TO THE RIGHT TABLE NAME?
attr_accessible :rv_tbl_title, :rv_tbl_id, :rv_tbl_text, :rv_tbl_author, :rv_tbl_ref_prod
has_one :Product, foreign_key: :rv_tbl_author
end
Is there a gem for it? What's the solution to use in the Review class questions?
I'm not sure if this first part is necessary or not, but in your database.yml file, make a new connection by adding something like this to the end:
review:
adapter: sqlserver
database: reviews_db
.... put your other configuration info here
Then in your review model review.rb:
class Review < ActiveRecord::Base
establish_connection :review
self.table_name = "review_table"
self.primary_key = "review_id"
end
Change the table name to the correct table, and the primary key to the correct column name.
Then create a new table/model for the sole purpose of having a local reference to a review. So you could call it ReviewReference
class ReviewReference < ActiveRecord::Base
belongs_to :review
has_one :product
end
And change your Product model to
class Product < ActiveRecord::Base
has_many :reviews, class_name: "ReviewReference"
end
This should get you a long way toward your goal. You might have to end up doing a lot of
#reviews = Review.where("some_column = ?", some_value)
or
#reviews = Review.find_by_sql("Some SQL Here") if you're doing more complex queries.
Sorry my answer isn't more concrete, I've only done this once. Props to Chad Fowler's Rails Recipes book for the concept.
So I'm trying to perform queries on a models great granchildren. The relationship is as so...
Tournament > Competitions > Matches > Players
and the Tournament model:
class Tournament < ActiveRecord::Base
has_many :competitions, :dependent => :destroy
has_many :matches, :through => :competitions
has_many :players, :through => :matches
end
Currently what I have for retrieving all the great great grandchildren records is:
#results = #tournament.competitions.collect{|b| b.matches.collect{|c| c.players.with_win_count}}.flatten
And this works, however the problem is that it returns an array. What I'm trying to do is build a dynamic query based on user input, and the player table is essentially a result pool for all matches and the user has the option to filter only what he wants to see. What I have in the end is a fairly potentially complex (depending on user input) query and the additional where clauses cannot be performed on an array. To give you a better idea of how this is meant to work, here is my code...
def results
#tournament = Tournament.find(params[:id])
#results = #tournament.all_great_grandchildren
#results.where(params[:player_condition1]) if params[:player_condition1]
#results.where(params[:player_condition2]) if params[:player_condition2]
#results.where(params[:player_condition3]) if params[:player_condition3]
if params[:match_condition]
#Join to match table so we can query the match fields
#results.join(:match)
#results.where(params[:match_condition])
end
....
#results.order(params[:order]) if params[:order]
end
Is there a way to find all of the great grandchildren (player) records for any given tournament without an array so I can continue to condition the records?
I think you should just call #tournament.players will work out of the box if you have setup with the above associations.
Chamnap has got it. The reason we define “has_many” and “belongs_to” associations in models is to give us easy access to the association models.
So when you have a Tournament object called tournament, you can do
competitions = tournament.competitions
matches = tournament.matches
players = tounaments.players
You can also go in the reverse direction, starting with a player object. Ex: matches = player.matches
With Active Record you shouldn't have to do manual joins or raw SQL model calls when dealing with associations. Have a quick read of the Actice Records Associations guide:
http://guides.rubyonrails.org/association_basics.html