I have three models. Two are related through a has_and_belongs_to_many association with the appropriate join table and one with an has_many association.
class Item < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :colors
end
class Color < ActivRecord::Base
belongs_to :item
end
class User < ActiveRecord::Base
has_and_belongs_to_many :items
end
I can create new items with a color in the following way:
#item = Item.new(name: "ball")
#item.users << #user
#item.save
#item.colors.create!(name: "blue")
The item is now linked to the user referenced by #user.
But I think there has to be another way to create items for users, like the way I added the color.
#user.item.create!(name: "car")
This doesn't work because the users-array of the created item is empty and the item does now not belong to a user.
What is the problem with this approach?
has_and_belongs_to_many should not be used. ( github )
Instead, use a has_many with the through flag: guides.rubyonrails.org...
Something like:
class Item < ActiveRecord::Base
has_many :user_items
has_many :users, through: :user_items # I can never think of a good name here
has_many :colors
has_many :item_colors, through: :colors
end
class Color < ActiveRecord::Base
has_many :item_colors
has_many :items, through: :item_colors
end
class User < ActiveRecord::Base
has_many :user_items
has_many :items, through: :user_items
end
class ItemColor < ActiveRecord::Base
belongs_to :item
belongs_to :color
end
class UserItem < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
It may seem complicated, but it will solve your problems. Also, consider the situation where many items share the same color, if there isn't a join table there, it would be more difficult to categorize items by color.
Related
I have taken a look at Polymorphic associations and STI but I don't really know if they apply to my specific use case. In my app I have the following relevant classes:
class Restaurant < ApplicationRecord
belongs_to :owner, class_name: "User"
has_many :menus
has_many :dishes
has_many :categories
has_many :bookings
has_many :simple_bookings
mount_uploader :photo, PhotoUploader
end
class Dish < ApplicationRecord
has_many :menu_dishes
has_many :menus, through: :menu_dishes
belongs_to :category
belongs_to :restaurant
mount_uploader :photo, PhotoUploader
end
class Menu < ApplicationRecord
belongs_to :restaurant
has_many :bookings
has_many :categories
has_many :menu_dishes
has_many :dishes, through: :menu_disheshas_many :menu_dishes
end
class MenuDish < ApplicationRecord
belongs_to :dish
belongs_to :menu
end
So far the user flow is the following:
A restaurant owner signs up and creates restaurant dishes. Then this owner creates a menu containing different MenuDishes.
Afterwards, users can search for different menus and book them.
My problem is the following:
I want to implement a feature where owners can add up to 3 MenuDishOptions (that belong to MenuDishes) so that a user can change each MenuDish with the available MenuDishOption for that MenuDish.
In other words, I want each MenuDish to contain many MenuDishOptions. Once a MenuDishOption is selected, the MenuDish passes to be a MenuDishOption and the selected MenuDishOption to a MenuDish.
Therefore the MenuDishOption class would look something like this:
class MenuDishOption < ApplicationRecord
belongs_to :menu_dish
has_one :dish
belongs_to :menu, through: menu_dishes
end
The MenuDish class would be updated to:
class MenuDish < ApplicationRecord
has_many :menu_dish_options
belongs_to :menu
belongs_to :dish
end
Please let me know if I need to share more info and thanks a million to anyone that takes the time to help this newbie.
I have three tables: illnesses, symptoms and a third table to map the relationship between the first two, called symptom_illness.
This third table has symptom_id, illness_id and its own id
I need a way to show, for example, all symptoms of a given "Common Cold" illness. In this example, "Common Cold" has an id of 1 and its symptoms have ids of 1 through 5.
This means that symptom_illness has 5 entries, where:
symptom_illness.illness_id = 1, symptom_id = 1
symptom_illness.illness_id = 1, symptom_id = 2
symptom_illness.illness_id = 1, symptom_id = 3
And so on. I need a way to display, in a single page, all the symptoms that have the same illness_id but I can't seem to find a way how to.
EDIT 1: My classes are related as such:
Symptom:
has_many :symptom_illness
has_many :illnesses, through: :symptom_illness
And similar for Illness.
Illness_symptom has belongs_to :symptom and belongs_to :illness
You have three models
class Symptom
has_many :symptom_illnesses
has_many :illnesses, through: :symptom_illnesses
end
class SymptomIllness
belongs_to :illness
belongs_to :symptom
end
class Illness
has_many :symptom_illnesses
has_many :symptoms, through: :symptom_illnesses
end
You can easily access al illness symptoms with something like that:
Illness.find(1).symptoms.each do |symptom|
# do something with this symptom
end
What do you want to show in the page?
If the symptom has a name attribute you can initialize a
#symptoms = Illness.find(1).symptoms
array in controlller and in the page you do something like
<% #symptomps.each do |symptom| %>
<% = symptom.name %>
<% end %>
You should use a has_many through: relationship.
class Illness < ActiveRecord::Base
has_many :symptom_illness
has_many :symptom, through: :symptom_illness
end
class SymptomIllness < ActiveRecord::Base
belongs_to :symptom
belongs_to :illness
end
class Symptom < ActiveRecord::Base
has_many :symptom_illness
has_many :illness, through: :symptom_illness
end
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Class Symptom < ActiveRecord::Base
belongs_to :symptom_illness
has_many :ilnesses, through: symptom_illness
end
class SymptomIllness < ActiveRecord::Base
belongs_to :symptom
belongs_to :illness
scope :ilness, ->(*i) {
where(ilness_id: i.flatten.compact.uniq
}
end
class Illness < ActiveRecord::Base
belongs_to :symptom_illness
has_many :symptoms, through: :symptom_illness
end
Ilness.find(1).symptoms
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
We have these models setup for users, categories and favorites:
class Favorite < ActiveRecord::Base
belongs_to :favoritable, polymorphic: true
belongs_to :user, inverse_of: :favorites
end
class User < ActiveRecord::Base
has_many :favorites, inverse_of: :user
end
class Category < ActiveRecord::Base
has_many :favorites, as: :favoritable
end
There are also some other objects that can be favorited (SubCategories, etc), and I would like to be able to grab the Category objects directly instead of a list of favorites:
#categories = #user.favorites.where(favoritable_type: "Category")
Is there way to grab a list of the actual Category objects through this #user object?
Have you tried just setting up a realtionship in user?
class User < ActiveRecord::Base
has_many :favorites, inverse_of: :user
has_many :categories, through: :favorites
end
Suppose I have 3 Models like this (not sure if this is correct):
class User < ActiveRecord::Base
has_many :lessons
has_many :points, through: :progress
end
class Progress < ActiveRecord::Base
belongs_to :user
has_many :lessons
end
class Lesson < ActiveRecord::Base
belongs_to :progress
end
(The Progress table has user_id and lesson_id fields.)
How would I make it so calling #user.points would return the amount of entries into the Progress table. Also, how would I build a relationship?
class User < ActiveRecord::Base
has_many :progresses
has_many :lessons, through: :progresses
end
class Progress < ActiveRecord::Base
belongs_to :user
belongs_to :lesson
end
class Lesson < ActiveRecord::Base
has_many :progresses
end
First, you need to set up the association for progress on your User model, so that the through association will work:
class User < ActiveRecord::Base
has_many :lessons
has_many :progress
has_many :points, through: :progress
end
Then you'll need to define a method (or relation) of points on your Progress table. Or, if you simply want a count of records, you could do: #user.points.size