I have a many to many relationship and I want to get all the users of all the cars of one user. What is the most efficient join for that?
Basically, I want to know how to do the following but with activerecord
complex SQL query, many to many
class User < ApplicationRecord
def people_who_use_my_car
self.cars.users
end
end
In your controller or view or wherever you want to use it do:
#user = User.first
#users = #user.people_who_use_my_car
A straightforward way would be
class User < ApplicationRecord
def people_who_use_my_car
self.cars.inject([]).do |users, car|
users += car.users
users
end
end
end
But this is not as efficient as using the database as #poet suggests,
class User < ApplicationRecord
def people_who_use_my_car
User.joins(:cars).where(cars: {user: self})
end
end
Related
In my app controller looks pretty simple:
class ProductsController
before_action :set_company
def index
#products = company.products.includes(:shipment)
end
private
def set_company
#company = Company.find(params[:company_id])
end
end
But what I worry about is the #product inside Index action was properly declared? What if I'll have millions of products? is there more efficient solution?
Model relations below:
class Products < ApplicationRecord
belongs_to :company
has_many :shipment
end
class Company < ApplicationRecord
has_many :products
end
class Shipment < ApplicationRecord
belongs_to :products
end
There is definitely a problem if you have million of records because your query is going to be too big, in this case you can add pagination in any flavor or use any other strategy that reduce the number of records queried each time.
To do pagination in Rails you can use https://github.com/kaminari/kaminari but this is not the only strategy available to do this.
Let me show an example:
I have 2 models:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
scope :created_in, ->(start_date, end_date) { where(created_at: start_date..end_date) }
end
What I want is to get users that created post during a specific period:
users = User.includes(:posts).joins(:posts).merge(Post.created_in(start_date, end_date))
Is it somehow possible to cache posts that are in the where clause? So after I do
users.first.posts
it will show me exactly those posts that match the condition without producing any additional queries.
No, I don't think this is possible. Depending on the context, what you can do is to do a lookup table which you memoize / cache. Something like
User.all.each do |user|
posts = posts_by_user_id[user.id]
end
def posts_by_user_id
#_posts_by_user_id ||= posts.group_by(&:user_id)
end
def posts
Post.created_in(start_date, end_date)
end
I am using Rails as a JSON API. My database has the following structure: the City model has_many Users, which in turn has_many Businesses.
When I send a GET request to businesses#index, I want Rails to return all the businesses in a given city, not just for a given user. What's the best way to do this?
I've already tried the given code below as a first pass, which is returning an internal server error (500).
def index
#city = City.find(params[:city_id])
#users = #city.users
#businesses = #users.businesses
render json: #businesses
end
Can you try this?
Because of the #user is an array. so the error will occuring.
# city.rb
class City < ApplicationRecord
has_many :users
end
#user.rb
class User < ApplicationRecord
belongs_to :invoice
has_many :businesses
end
#business.rb
class Business < ApplicationRecord
belongs_to :user
end
# your controller
def get_business
#city = City.find(params[:id])
#business = #city.users.includes(:business)
render json: #business
end
Add a new relationship to your City model:
has_many businesses, through: users
Then when you have a specific city you can get all businesses:
#city.businesses
You might also want to try starting with the Business model to make the query.
Business.joins(user: :city).where(cities: { id: params[:city_id] })
joins uses the association names
where uses the table names
Suppose I have a User model
user.rb
class User < ActiveRecord::Base
...
end
with attributes like: name, username, access
access is an enum that tells me if the user is "staff" or "customer"
To get the name and username of the logged in user, I can do:
current_user.name
current_user.username
And suppose I have a Staff model
staff.rb
class Staff < ActiveRecord::Base
belongs_to :user
end
with attributes like: salary, phone_number
And I also have a Customer model
customer.rb
class Customer < ActiveRecord::Base
belongs_to :user
end
with attributes like: address, phone_number
I want to be able to call this on my staff's controller:
current_user.staff.salary
And this on my customer's controller:
current_user.customer.address
WHAT I TRIED SO FAR
I overwrote sessions_controller.rb
def create
super
model_name = current_user.access.capitalize.constantize
spec = model_name.where(user_id: current_user.id).take
session[:spec] = spec
end
So I'm able to access it via session[:spec], but not via current_user. Any ideas?
Well to begin with, your User model should reference the staff or customer, even if they are to stay blank
class User
has_one :staff
has_one :address
Just by doing this, you should be able to use current_user.customer.address. However...
I suggest you add some convenient methods in ApplicationController or a module that you include
def staff_signed_in?
#staff_signed_in ||= (user_signed_in? and current_user.access == :staff)
end
def current_staff
#current_staff ||= (current_user.staff if staff_logged_in?)
end
# same for customer
# Note that I use instance variables so any database queries are executed only once !
Then you can simply call
<% if customer_signed_in? %>
<h2>Logged in as customer</h2>
<p>Address : <%= current_customer.address %>
<% end %>
EDIT : about your concerns concerning database hits
You gave the example of current_user.customer.cart.products
This is indeed quite a nested association. My suggestion above already reduces it by one level (ie current_customer == current_user.customer). Then you have to go through carts to reach products... it isn't so bad in my opinion.
If you need to call that often (current_customercustomer.cart) you can override the current_customer for a given controller and eager load the resources you know you will use use.
def UserShopController < ApplicationController
# Let's assume current_customer is defined in ApplicationController like I showed above
# UserShopController always uses the customer cart, so let's load it right at the beginning
...
private
# Override with eager loading
def current_customer
#current_customer ||= (current_user.customer.includes(:cart) if customer_logged_in?)
end
add has_one :customer to your user.rb
Your user model should be like below to accessing related model.
class User < ActiveRecord::Base
has_one :customer
end
class User
has_one :super_admin
end
class SuperAdmin
belongs_to :user
end
How would I create a SuperAdmin instance associating a certain user, from withing the User model?
I'm looking for something like this (in the User model), but it's not working:
def promote_to_super
self.super_admin.create!
end
You can use create_association(attributes = {}):
def promote_to_super
self.create_super_admin
end
See more here.