Rails has_many OR condition - ruby-on-rails

I've got a task where Garage should fetch cars from User model or if User doesn't have cars Garage should fetch cars from itself.
Ideally, I want to have all rails API for has_many relation. Is there any chance to do that?
class Garage
has_many :cars, through: :user || has_many :cars # pseudo-code
end

I would just name the associations differently and use a method to load the cars:
has_many :cars
has_many :user_cars, through: :user, source: :cars
def all_cars
user_cars.presence || cars
end

The full code necessary to solve your problem is unclear, but broadly speaking here's how I would go about performing this structural migration.
Firstly, define two clear associations:
class Garage < ApplicationRecord
has_many :user_cars, through: :user, class_name: 'Car'
has_many :cars
end
Then add some method(s) that attempt to fetch both, as you described:
class Garage < ApplicationRecord
has_many :user_cars, through: :user, class_name: 'Car'
has_many :cars
def cars
user_cars.presence || super
end
end
Then, when the data has all been migrated to the new structure, you can delete the old association:
class Garage < ApplicationRecord
has_many :cars, through: :user
end

Related

Extracting data using rails query from a join table

I have users table, books table and books_users join table. In the users_controller.rb I am trying extract the users who have filtered_books. Please help me to resolve that problem.
user.rb
has_many :books_users, dependent: :destroy
has_and_belongs_to_many :books, join_table: :books_users
book.rb
has_and_belongs_to_many :users
books_user.rb
belongs_to :user
belongs_to :book
users_controller.rb
def filter_users
#filtered_books = Fiction.find(params[:ID]).books
#users = **I want only those users who have filtered_books**
end
has_and_belongs_to_many does not actually use a join model. What you are looking for is has_many through:
class User < ApplicationRecord
has_many :book_users
has_many :books, through: :book_users
end
class Book < ApplicationRecord
has_many :book_users
has_many :users, through: :book_users
end
class BookUser < ApplicationRecord
belongs_to :book
belongs_to :user
end
If you want to add categories to books you would do it by adding a Category model and another join table. Not by creating a Fiction model which will just create a crazy amount of code duplication if you want multiple categories.
class Book < ApplicationRecord
has_many :book_users
has_many :users, through: :book_users
has_many :book_categories
has_many :categories, through: :book_categories
end
class BookCategory < ApplicationRecord
belongs_to :book
belongs_to :category
end
class Category < ApplicationRecord
has_many :book_categories
has_many :books, through: :book_categories
end
If you want to query for users that follow a certain book you can do it by using an inner join with a condition on books:
User.joins(:books)
.where(books: { title: 'Lord Of The Rings' })
If you want to get books that have a certain category:
Book.joins(:categories)
.where(categories: { name: 'Fiction' })
Then for the grand finale - to query users with a relation to at least one book that's categorized with "Fiction" you would do:
User.joins(books: :categories)
.where(categories: { name: 'Fiction' })
# or if you have an id
User.joins(books: :categories)
.where(categories: { id: params[:category_id] })
You can also add an indirect association that lets you go straight from categories to users:
class Category < ApplicationRecord
# ...
has_many :users, though: :books
end
category = Category.includes(:users)
.find(params[:id])
users = category.users
See:
The has_many :through Association
Joining nested assocations.
Specifying Conditions on Joined Tables
From looking at the code i am assuming that Book model has fiction_id as well because of the has_many association shown in this line Fiction.find(params[:ID]).books. There could be two approaches achieve this. First one could be that you use #filtered_books variable and extract users from it like #filtered_books.collect {|b| b.users}.flatten to extract all the users. Second approach could be through associations using fiction_id which could be something like User.joins(:books).where(books: {id: #filtererd_books.pluck(:id)})

has_many :through and join table in Rails

I would like to implement properly database and associated models for my application.
I have two models: user and location. I would like them to be joined by a table due to the fact that I need to keep historic data about the association between these two entities.
Therefore, I created a join table called user_locations in addition to the foreign keys of location_id and user_id I have two additional fields which I need.
So far so good.
A new requirement has emerged and I need my location to be polymorphic.
I don't know how to set up my models properly to store to be able to have that joined table and a polymorphic association.
Here is what I came up with so far:
user.rb
class User < ApplicationRecord
has_many :locations, through: :user_locations, as: :locationable, source_type: 'User'
has_many :user_locations
end
location.rb
class Location < ApplicationRecord
belongs_to :locationable, polymorphic: true
has_many :users
end
user_location.rb
class UserLocation < ApplicationRecord
belongs_to :user
belongs_to :location
validates_presence_of :user
validates_presence_of :location
end
I found a very good tutorial/article that I think can help you here.
It would entail you doing something like:
class User < ApplicationRecord
has_many :locations
has_many :model_ones, through: :locations, source: :locationable, source_type: 'ModelOne'
has_many :model_twos, through: :locations, source: :locationable, source_type: 'ModelTwo'
And:
class Location < ApplicationRecord
belongs_to :locationable, polymorphic: true
belongs_to :user
end
Where ModelOne and ModelTwo are filled in with the models you need obviously.

Specifying Conditions on Multiple Nested Eager Loaded Associations

I'm trying to query on ActiveRecord multiple nested eager loaded associations with conditions like so:
user.books.includes(slot: [room: :school]).where("books.slot.room.school.id = 1")
Obviously this query is wrong, but basically what I'm trying to reach is a relation of user.books for a certain school.
My model structure is:
class User < ActiveRecord::Base
has_many :books
has_many :slots, through: :books
has_many :rooms, through: :slots
has_many :schools
end
class Book < ActiveRecord::Base
belongs_to :user
belongs_to :slot
end
class Slot < ActiveRecord::Base
has_many :books
belongs_to :room
end
class Room < ActiveRecord::Base
belongs_to :school
has_many :slots
end
class School < ActiveRecord::Base
has_many :users
has_many :rooms
has_many :slots, through: :rooms
has_many :books, through: :slots
end
Any ideas? Thanks in advance.
includes eager loads association records, while joins does what you want to do.
This question has been asked here numerous times. Please make sure that you look and try to find a similar question here before you ask one.
So you want to change your code like this:
user.books.joins(slot: [room: :school]).where(schools: { id: 1 })

Active Record Associations: has_and_belongs_to_many, has_many :through or polymorphic association?

The Ruby on Rails app I am working on allows users to create and share agendas with other users.
In addition, we must be able to:
Display a list of agendas for each user, on his profile
Display a list of users associated with an agenda, on the agenda's page
When sharing an agenda with another user, define a role for this user, and display the role of this user on the list mentioned right above
I was going to go with a has_and_belongs_to_many association between the user and the agenda models, like that:
class User < ActiveRecord::Base
has_and_belongs_to_many :agendas
end
class Agenda < ActiveRecord::Base
has_and_belongs_to_many :users
end
But then I wondered whether this would let me get and display the #user.agenda.user.role list of roles on the given agenda page of a given user.
And I thought I should probably go with a has_many :through association instead, such as:
class User < ActiveRecord::Base
has_many :roles
has_many :agendas, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class Agenda < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
And although I was pretty comfortable about the idea of a user having several roles (one for each agenda), I am not sure about the idea of an agenda having several roles (one for each user?).
Finally, to add to the confusion, I read about the polymorphic association and thought it could also be a viable solution, if done this way for instance:
class Role < ActiveRecord::Base
belongs_to :definition, polymorphic: true
end
class User < ActiveRecord::Base
has_many :roles, as: :definition
end
class Agenda < ActiveRecord::Base
has_many :roles, as: :definition
end
Does any of the above solutions sound right for the situation?
UPDATE: Doing some research, I stumbled upon this article (from 2012) explaining that has_many :through was a "smarter" choice than has_and_belongs_to_many. In my case, I am still not sure about the fact that an agenda would have many roles.
UPDATE 2: As suggested in the comments by #engineersmnkyn, a way of solving this would be to go with two join tables. I tried to implement the following code:
class User < ActiveRecord::Base
has_many :agendas, through: :jointable
end
class Agenda < ActiveRecord::Base
end
class Role < ActiveRecord::Base
end
class Jointable < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
has_many :agendaroles through :jointable2
end
class Jointable2 < ActiveRecord::Base
belongs_to :roles
belongs_to :useragenda
end
I am not sure about the syntax though. Am I on the right track? And how should I define the Agenda and the Role models?
UPDATE 3: What if I went with something like:
class User < ActiveRecord::Base
has_many :roles
has_many :agendas, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class Agenda < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
and then, in the migration file, go with something like:
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.belongs_to :user, index: true
t.belongs_to :agenda, index: true
t.string :privilege
t.timestamps
end
end
end
Would I be able to call #user.agenda.privilege to get the privilege ("role" of creator, editor or viewer) of a given user for a given agenda?
Conversely, would I be able to call #agenda.user.privilege ?
Okay I will preface by saying I have not tested this but I think one of these 2 choices should work well for you.
Also if these join tables will never need functionality besides a relationship then has_and_belongs_to_many would be fine and more concise.
Basic Rails rule of thumb:
If you need to work with the relationship model as its own entity, use has_many :through. Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship itself.
First using your example (http://repl.it/tNS):
class User < ActiveRecord::Base
has_many :user_agendas
has_many :agendas, through: :user_agendas
has_many :user_agenda_roles, through: :user_agendas
has_many :roles, through: :user_agenda_roles
def agenda_roles(agenda)
roles.where(user_agenda_roles:{agenda:agenda})
end
end
class Agenda < ActiveRecord::Base
has_many :user_agendas
has_many :users, through: :user_agendas
has_many :user_agenda_roles, through: :user_agendas
has_many :roles, through: :user_agenda_roles
def user_roles(user)
roles.where(user_agenda_roles:{user: user})
end
end
class Role < ActiveRecord::Base
has_many :user_agenda_roles
end
class UserAgenda < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
end
class UserAgendaRoles < ActiveRecord::Base
belongs_to :role
belongs_to :user_agenda
end
This uses a join table to hold the relationship of User <=> Agenda and then a table to join UserAgenda => Role.
The Second Option is to use a join table to hold the relationship of User <=> Agenda and another join table to handle the relationship of User <=> Agenda <=> Role. This option will take a bit more set up from a CRUD standpoint for things like validating if the user is a user for that Agenda but allows a little flexibility.
class User < ActiveRecord::Base
has_many :user_agendas
has_many :agendas, through: :user_agendas
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
def agenda_roles(agenda)
roles.where(user_agenda_roles:{agenda: agenda})
end
end
class Agenda < ActiveRecord::Base
has_many :user_agendas
has_many :users, through: :user_agendas
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
def user_roles(user)
roles.where(user_agenda_roles:{user: user})
end
end
class Role < ActiveRecord::Base
has_many :user_agenda_roles
end
class UserAgenda < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class UserAgendaRoles < ActiveRecord::Base
belongs_to :role
belongs_to :user
belongs_to :agenda
end
I know this is a long answer but I wanted to show you more than 1 way to solve the problem in this case. Hope it helps

Polymorphic Association with same model Ruby on Rails 4

So I have an app in which users can create cars. They can also like cars and I want to create an association between both. Cars that they created belong to them and Cars that they have liked belong to them through the context of liking them. To do this I have set up my associations as follows:
User Association:
class User < ActiveRecord::Base
has_many :cars
has_many :cars, -> {distinct}, through: :likes
end
Car Association:
class Car < ActiveRecord::Base
belongs_to :users
has_many :likes
has_many :users, -> { distinct }, through: :likes
end
Like Association:
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :car
end
The problem is that before I had my user has_many cars through like relationship declared. I used to be able to call #user.cars and it would present the user's cars. Now it returns the collection of the cars the user has liked. I need methods for each collection.
When I try: User.likes.cars
I get a
No Method error
and the console log looks through the likes records and still doesn't return the cars even though my likes records have a car_id field.
I have looked at a bunch of questions but have trouble understanding them. I've also tried to define methods in the model and nothing is seeming to work. Any help is appreciated.
How would I be able to change my associations so I can have a for query both User.cars (for cars the user has created) and User.likes.cars (for cars the user has liked)?
So the below answer from Oleg didn't work exactly but led me in the right direction. Thank you! I started by following the above example and doing:
class User < ActiveRecord::Base
has_many :cars
has_many :car_likes, -> {distinct}, class_name: 'Car', through: :likes
end
class Car < ActiveRecord::Base
belongs_to :users
has_many :likes
has_many :user_likes, -> { distinct }, class_name: 'User', through: :likes
end
This returned the following error in console:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) "car_likes" or :car_like in model Like. Try 'has_many :car_likes, :through => :likes, :source => '. Is it one of user or car?
So I changed it to:
class User < ActiveRecord::Base
has_many :cars
has_many :car_likes, -> {distinct}, through: :likes, source: :cars
end
Car Association:
class Car < ActiveRecord::Base
belongs_to :users
has_many :likes
has_many :user_likes, -> { distinct }, through: :likes, source: :users
end
It nows works for both models! Thanks and hopefully this is helpful to someone else with the same problem.
has_many :cars, -> {distinct}, through: :likes overrides has_many :cars because it redefines User.cars. Try the following:
class User < ActiveRecord::Base
has_many :cars
has_many :car_likes, -> {distinct}, class_name: 'Car', through: :likes
end
Car Association:
class Car < ActiveRecord::Base
belongs_to :users
has_many :likes
has_many :user_likes, -> { distinct }, class_name: 'User', through: :likes
end
#To get them, instead of user.likes.cars
#user.car_likes
#car.user_likes
Please let me know if the problem persists. There might be another error.
I don't see where you are defining any model as polymorphic.
In the past I have done something like this.. actually I did this for tags/taggings and made "like" a tag a user applied to another instance. This is an ad-hoc modification and I may have missed something but it's a pretty common use case for polymorphic associations.
class Like < ActiveRecord::Base
belongs_to :likeable, polymorphic: true
...
end
class Liking < ActiveRecord::Base
belongs_to :like
belongs_to :likeable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :likings, :as => :likeable
has_many :likes, -> { order(created_at: :desc) }, :through => :taggings
end

Resources