I have 2 models Event and User and two models for :through associations - event_member and event_organizer. I create few assocaitions, and in regular use it works, but in rails admin on Events and Event Organizer page I have an error:
ActiveRecord::AssociationNotFoundError in RailsAdmin
Association named 'event' was not found on EventOrganizer
When I change associations I start to get other RailsAdmin errors
schema.rb:
#event_members
t.bigint "member_id"
t.bigint "event_id"
#event_organizers
t.bigint "organizer_id"
t.bigint "organized_event_id"
In models I have next code:
Event
has_many :event_members
has_many :members, through: :event_members, source: :user
has_many :event_organizers
has_many :organizer, through: :event_organizers, source: :user
User
has_many :event_members, foreign_key: :member_id
has_many :events, through: :event_members
has_many :event_organizers, foreign_key: :organizer_id
has_many :events, through: :event_organizers, source: :user
EventOrganizer
belongs_to :user, foreign_key: :organizer_id
belongs_to :event, foreign_key: :organized_event_id
EventMember
belongs_to :user, foreign_key: :member_id
belongs_to :event
You have two definitions of has_many :events in User, and should change their names to make them unique and meaningful.
It was dummy problem with wrong scope, it was:
has_many :ogranized_event, through: :event_organizers, source: :event
Must be:
has_many :event_organized, through: :event_organizers, source: :organized_event
Related
I have the following model associations setup were a consumer can favorite a product or a variant. I just wanted to ask if my approach is correct?
class Favorite < ApplicationRecord
belongs_to :consumer
belongs_to :favorited, polymorphic: true
belongs_to :product, optional: true
belongs_to :variant, optional: true
end
class Consumer < ApplicationRecord
has_many :favorites
has_many :favorite_products, through: :favorites, source: :favorited, source_type: 'Product'
has_many :favorite_variants, through: :favorites, source: :favorited, source_type: 'Variant'
end
class Product < ApplicationRecord
has_many :favorites, dependent: :destroy
end
class Variant < ApplicationRecord
has_many :favorites, dependent: :destroy
end
class CreateFavorites < ActiveRecord::Migration[6.0]
def change
create_table :favorites do |t|
t.references :consumer, index: true
t.references :favorited, polymorphic: true, index: true
t.integer :product_id
t.integer :variant_id
t.timestamps
end
end
end
It is not clear why you have both polymorphic favorited and separate relation on product_id/variant_id. Usually one would go with one of these approaches.
Polymorphic associations can be unidirectional too - has_many :favorites, as: :favorited, dependent: :destroy
If you go with separate columns for each relation - indexes on product_id and variant_id may also be useful to prevent full table scan on product/variant deletion (on dependent: :destroy, also if you do not expect callbacks/nested relations there - delete_all is faster).
I have a relationship model in which two Users can enter into a Trade for the exchange of two Items.
class User < ActiveRecord::Base
has_many :owned_items, class_name: "Item"
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :trades
has_many :trades
has_many :wanted_items, class_name: "Item", through: :trades, source: :item
end
class Item < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: :user_id
has_many :trades, dependent: :destroy
has_many :trade_requesters, through: :trades
has_many :trade_recipients, through: :trades
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item", foreign_key: :wanted_item_id
belongs_to :collateral_item, class_name: "Item", foreign_key: :collateral_item_id
end
The migration on my Trades table looks like this:
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
The stack trace leads to a helper method I'm using to list all Trade requests. That line says #trades = current_user.trades_received.requested.count, and then on down to the model association on User where has_many :owned_items, class_name: "Item". From my understanding, it looks like the trades_received method, which is called through: :owned_items and source: :trades should be referencing the :wanted_item_id foreign key in the migration. But it is not. It works if I create a migration to add item_id, but a Trade needs two items, and so I've split it up into the two wanted_item and collateral_item associations. How do I set that User association up so that it references the Item that is being requested by another User? Should Items has_many :trades, the way I have it, or should Items belongs_to :trades?
Full error:
PG::UndefinedColumn: ERROR: column trades.item_id does not exist
LINE 1: ...LECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."...
^
: SELECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."item_id" = "items"."id" WHERE "items"."user_id" = $1 AND "trades"."approved" IS NULL
tldr: I need to track a bunch of complex has_many :through associations, I don't think my data model is correct, and need help understanding why. Thank you.
You're setting up two has_many :through relationship between User and Item, with Trade as the join table for both. You got some relationship confused. Here is a setup based on your migration:
class User < ActiveRecord::Base
has_many :received_trades, class_name: "Trade", foreign_key: "trade_recipient"
has_many :requested_trades, class_name: "Trade", foreign_key: "trade_requester"
has_many :collateral_items, through: :received_trades
has_many :wanted_items, through: :requested_trades
end
class Item < ActiveRecord::Base
has_many :collateral_items, class_name: "Trade", foreign_key: "collateral_item"
has_many :wanted_items, class_name: "Trade", foreign_key: "wanted_item"
has_many :trade_requesters, through: :wanted_items
has_many :trade_recipients, through: :collateral_items
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item"
belongs_to :collateral_item, class_name: "Item"
end
##migration
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
Some explanation:
Item has_many :collateral_item ## item_id in table collateral_items
Item has_many :collateral_item, class_name: "Trade", foreign_key: "collateral_item"
##collateral_item_id in trades table.
Ok, well. The problem is here:
has_many :trades, dependent: :destroy
And in your Trade model:
belongs_to :wanted_item, ...
belongs_to :collateral_item, ..
Rails cannot handle this automatically.
You need to do one of this steps (depending on what you need in your app):
If you need separate associations:
class User < ActiveRecord::Base
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :wantable_trades
end
class Item < ActiveRecord::Base
has_many :wanted_trades, class_name: 'Trade', inverse_of: :wanted_item, dependent: :destroy
has_many :collateral_trades, class_name: 'Trade', inverse_of: :collateral_item, dependent: :destroy
end
If you need all trades as single association:
Well, you will have a pain in the ass :) In this case you should either select associations manually, or rethink your data model.
I have a User model:
class User < ActiveRecord::Base
has_many :tracks, dependent: :destroy
has_many :tracked_locations, through: :tracks, source: :tracking, source_type: 'Location'
and a Track model (think of it as 'following'):
class Track < ActiveRecord::Base
belongs_to :user
belongs_to :tracking, polymorphic: true
end
The idea here is I will have many models to track / follow so I am using polymorphism. For example I have a Location model:
class Location < ActiveRecord::Base
has_many :tracks, :as => :tracking, :dependent => :destroy
has_many :users, through: :tracks
Now in the console Location.first.users works fine along with User.first.tracked_locations.
Now I will be adding another polymorphic relationship along the lines of Flagged. The user can 'flag' another model with a note etc. So if I add has_many :users, through: :flagged to the Location model for example I need to differentiate between tracking users and flagged users.
I tried:
has_many :tracking_users, through: :tracks, source: :tracking, source_type: 'User'
but I get:
NoMethodError: undefined method `evaluators_for' for #<Location:0x007ff29e5409c8>
Can I even do this or am I missing something simple here?
UPDATE
Based on the answer below I figured it out:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
I'm not 100% on this, but you could try:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
Or you could also just create a class method and do it by hand.
def self.tracking_users
user_ids = tracks.collect(&:user_id)
User.where(id: user_ids)
end
edit: Had a brainfart, changed the "source" up there to :user. That tells what table to actually do the lookup in with the other attribute you've provided. of course it wouldn't be in :tracks
I'm a designer/HTML+CSS dev learning Rails, and I'm having trouble with multiple user roles. My main models are as follows:
Studio
has_many :locations, dependent: :destroy
has_many :events, through: :locations
has_many :teachers, through: :events
Location
belongs_to :studio, :class_name => Studio, :foreign_key => "studio_id"
has_many :events
has_many :teachers, through: :events
Event
belongs_to :location, :class_name => Location, :foreign_key => "location_id"
belongs_to :studio
has_many :teachers
Teacher
belongs_to :event, :class_name => Event, :foreign_key => "event_id"
belongs_to :location
belongs_to :studio
has_one :user, :as => :roleable
accepts_nested_attributes_for :user
User
belongs_to :roleable, :polymorphic => true
The Teacher/User relationship is the tricky bit; I'm not sure if it should be reversed.
Users have basic profile info, Teachers have many details. All users can follow teachers, but teachers can also invite new teachers, and list other teachers as influences. I had this set up as
Teacher
has_many :guests, class_name: "Teacher", foreign_key: "host_id"
belongs_to :host, class_name: "Teacher"
has_many :influences, class_name: "Teacher", foreign_key: "student_id"
belongs_to :student, class_name: "Teacher"
User
has_many :favorites, class_name: "User", foreign_key: "fan_id"
belongs_to :fan, class_name: "User"
Does this look right, or should I say that User has_one :teacher ? The latter seems more correct, but it made my views really difficult. Thanks in advance!
I'm having a lot of issues with my messaging system. I would love some help. It works fully, except the User Message relation. I'm attempting to relate authored_messages to User and received_messages to User. The roadblock from doing this is that received_messages is supposed to go through two different models (ConversationUser and Conversation), whereas authored_messages should be a direct relationship using the message's user_id field. Here she is:
Models
User:
class User < ActiveRecord::Base
has_many(
:conversation_users,
inverse_of: :user,
dependent: :destroy
)
has_many(
:conversations,
through: :conversation_users
)
# has_many :messages ?
# has_many( ?
# :received_messages,
# class_name: "Messages",
# through: :conversations,
# source: :conversation_users
# )
Message:
class Message < ActiveRecord::Base
belongs_to :user # ?
belongs_to :recipient, class_name: "User" # ?
belongs_to(
:conversation,
inverse_of: :messages
)
has_many(
:conversation_users,
through: :conversation
)
accepts_nested_attributes_for :conversation
Conversation:
class Conversation < ActiveRecord::Base
has_many(
:messages,
inverse_of: :conversation
)
has_many(
:conversation_users,
inverse_of: :conversation
)
has_many(
:users,
through: :conversation_users
)
accepts_nested_attributes_for :messages
ConversationUser (a join model):
class ConversationUser < ActiveRecord::Base
belongs_to(
:user,
inverse_of: :conversation_users
)
belongs_to(
:conversation,
inverse_of: :conversation_users
)
has_many(
:messages,
through: :conversation
)
delegate :users, to: :conversation
accepts_nested_attributes_for :conversation
Migrations
User
# None involved with messaging
Message
t.integer :user_id
t.integer :conversation_id
t.text :body
Conversation
# None, except the :id field. It helps relate messages and conversation_users
ConversationUser
t.integer :user_id
t.integer :conversation_id
It seems to me that I'd be adding something like this (how do I associate one model twice to another), but I'm having a hard time applying this to my models. And to further clarify how these associations work, this is a really simplified visual:
users--->conversation_users
: |
: |
V V
messages<---conversation
I'd really, really appreciate any help I can get, and hopefully this question will help other people dealing with complex associations!
EDIT
I forgot to mention it, but this messaging system can have as many recipients (conversation_users) as the sender decides. (when the sender creates a new conversation)
How do you know whether a message is sent or received?
In your message model, you're only referencing user_id and conversation_id. You'd typically want recipient_id and sender_id instead of just user_id:
class User < ActiveRecord::Base
has_many :conversation_users, inverse_of: :user, dependent: :destroy
has_many :conversations, through: :conversation_users
end
class Conversation < ActiveRecord::Base
has_many :conversation_users
has_many :users, through: conversation_users
has_many :sent_messages, class: "Message", through: :conversations, foreign_key: "sender_id", source: :user
has_many :received_messages, class: "Message", through: :conversations, foregin_key: "recipient_id", source: :user
end
class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :sender, class_name: "User", primary_key: "sender_id"
belongs_to :recipient, class_name: "User", primary_key: "recipient_id"
end
messages
t.integer :sender_id
t.integer :recipient_id
t.integer :conversation_id
t.text :body
This should yield:
#user.conversations[x].sent_messages
#user.conversations[x].received_messages
I ended up figuring it out via further trial and error. I was making it much harder than it was. Here are the correct changes for the User and Message classes and the relations between them:
Message
class Message < ActiveRecord::Base
belongs_to :user # This
belongs_to(
:conversation,
inverse_of: :messages
)
has_many(
:conversation_users,
through: :conversation
)
has_many( # This
:recipients,
class_name: "User",
through: :conversation_users,
source: :user
)
accepts_nested_attributes_for :conversation
User
class User < ActiveRecord::Base
has_many(
:conversation_users,
inverse_of: :user,
dependent: :destroy
)
has_many(
:conversations,
through: :conversation_users
)
has_many :messages # This
has_many( # This
:received_messages,
class_name: "Message",
through: :conversations,
source: :messages
)
The associations I marked with # This are the four relations I was having trouble connecting in the first place. This messaging system is a unique four-model messaging system that supports (hypothetically) limitless conversation users. Its behavior is not unlike many other social network messaging systems out there. It is fully functional, and any of these four class objects can be accessed from any other related class object. I hope this Q/A will serve as a great reference for anyone trying to build a similar messaging system. Cheers!