I am using Ruby on Rails 3 and I would like to set a polymorphic association using namespaced classes.
Migrations are:
create_table :users_users do |t|
t.integer :id
t.string :full_name
t.references :userable, :polymorphic => true
end
create_table :users_profiles do |t|
t.integer :id
...
end
create_table :users_accounts do |t|
t.integer :id
...
end
Classes are:
class Users::User < ActiveRecord::Base
# Association ...
end
class Users::Profile < ActiveRecord::Base
# Association ...
end
class Users::Account < ActiveRecord::Base
# Association ...
end
How I must write code associations for above classes (using :class_name => "Users:User", ...?) in order to auto-create and auto-destroy associated model records, "mapping" those in the users_users table and viceversa?
Do you have some advice about that? What string values I will have in userable_type attributes (example: 'Users::Profile', 'Profile', ...)?
To setup the associations, you don't need to use class name...
class Users::User < ActiveRecord::Base
belongs_to :userable, :polymorphic => true
end
class Users::Profile < ActiveRecord::Base
has_one :user, :as => :userable, :dependent => :destroy
end
class Users::Account < ActiveRecord::Base
has_one :user, :as => :userable, :dependent => :destroy
end
:dependent => :destroy will deal with deleting them when the Users::User is destroyed, but in terms of creating, you've got the same options as you have with normal relationships. If you're doing it from a form, it's best to use nested attributes.
In the database, the userable_type column would include the namespace. So it'd be 'Users::Account' or 'Users::Profile'.
Related
I am trying to connect two classes (conversation and user) by a many-to-many relationship in Ruby on Rails. I set them both up and added a connection table called conversations_custom_users to connect them and it was working. Once we needed our User model to inherit from another User model, setting conversations in a user object was failing and looking for a connection table with the parent class.
My classes and the conversation migration looks like below (I haven't modified the User migration for the many-to-many relationship):
class CustomUser < Spree::User
serialize :resources, Array
has_and_belongs_to_many :conversations, :foreign_key => :conversation_ids, class_name: 'Conversation'
end
class Conversation < ApplicationRecord
has_and_belongs_to_many :receiver, :foreign_key => :receiver_id, class_name: 'CustomUser'
end
class CreateConversations < ActiveRecord::Migration[6.1]
def change
create_table :conversations do |t|
t.timestamps
end
create_table :conversations_custom_users, id: false do |t|
t.belongs_to :conversation, foreign_key: 'conversation_id', index: true
t.belongs_to :custom_user, foreign_key: 'receiver_id', index: true
end
end
end
I think I shouldn't need to add another table called conversations_spree_users, but I also tried adding one. It didn't solve the problem since then Rails was looking for a spree_user_id field. I also tried adding the spree_user_id field to the conversations_spree_users table, but it wouldn't migrate because it was a duplicate column name!
I think I'm missing something about many-to-many relations or inheritance or both in Ruby. If someone can help with this issue I'd really appreciate it.
you could use polymorphic associations to build many-to-many association, the benefit of this approach is that you can use only one join-table for all user's hierarchy inheritance.
class CreateConversationals < ActiveRecord::Migration[6.1]
def change
create_table :conversationals do |t|
# ...
t.references :contributor, polymorphic: true, null: false
t.integer :conversation_id
t.timestamps
end
end
end
class Conversational < ApplicationRecord
belongs_to :contributor, polymorphic: true
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :conversationals, :foreign_key => :conversation_id
has_many :custom_users, :through => :conversationals, :source => :contributor, :source_type => 'CustomUser'
has_many :other_users, :through => :conversationals, :source => :contributor, :source_type => 'OtherUser'
end
class CustomUser < Spree::User
has_many :conversationals, as: :contributor
has_many :conversations, :through => :conversationals, :as => :contributor
end
# i assume you use STI
class OtherUser < CustomUser
end
then
user1 = CustomUser.create(...)
user2 = OtherUser.create(...)
conversation = Conversation.create(...)
conversational1 = Conversational.create(..., conversation_id: conversation.id, contributor: user1)
conversation1 = Conversational.create(..., conversation_id: conversation.id, contributor: user2)
# many-to-many
user1.conversations
user2.conversations
conversation.custom_users
conversation.other_users
I have a 2 models Game & Theme and they have a has_and_belongs_to_many association. I have tried many solutions to prevent duplicate records in the games_themes table, but no solutions work. The problem is, games_themes is a table, but it is not a model, so I can't figure out a way to run validations on it effectively.
Heres a solution I tried
class Theme < ActiveRecord::Base
has_and_belongs_to_many :games, :uniq => true
end
class Game < ActiveRecord::Base
has_and_belongs_to_many :themes, :uniq => true
end
You should use database-level validation:
#new_migration
add_index :games_themes, [:game_id, :theme_id], :unique => true
HABTM
This will prevent you saving any duplicate data in the database. Takes the burden off Rails & ensures you only have game or theme. The problem is because HABTM doesn't have a model, there's no validation you can perform in Rails, meaning you need to make it db-level
As mentioned in the comments, this means you'll have to handle the exceptions raised from the db like this:
#app/controllers/games_controller.rb
def create
#creation stuff here
if #game.save
#successful save
else
#capture errors
end
end
Use:
validates_uniqueness_of :theme_id, :scope => :game_id
As follows:
class Theme < ActiveRecord::Base
has_many :games, through: :games_themes
end
class Game < ActiveRecord::Base
has_many :themes, through: :games_themes
end
class GamesThemes < ActiveRecord::Base
belongs_to :game
belongs_to :theme
validates_uniqueness_of :theme_id, :scope => :game_id
end
To run validations on join table you should use has_many :through association instead.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Creating new Model GameTheme for validation purpose is not a good idea. We can validate itself in migration.
Theme Model:
class Theme < ActiveRecord::Base
has_and_belongs_to_many :games,
:association_foreign_key => 'theme_id',
:class_name => 'Theme',
:join_table => 'games_themes'
end
Game Model:
class Theme < ActiveRecord::Base
has_and_belongs_to_many :games,
:association_foreign_key => 'game_id',
:class_name => 'Game',
:join_table => 'games_themes'
end
games_themes migration:
You can add uniqueness to join table, Have a look here for more detail.
class GamesThemesTable < ActiveRecord::Migration
def self.up
create_table :games_themes, :id => false do |t|
t.references :game
t.references :theme
end
add_index :games_themes, [:theme_id, :game_id], :unique => true
end
def self.down
drop_table :games_themes
end
end
I am trying to add a "following" like functionality to my site but I am having trouble finding the right way to use a polymorphic association. A user needs to be able to follow 3 different classes, these 3 classes do not follow the user back. I have created a user following user in the past but this is proving to be more difficult.
My Migration was
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :relations_id
t.string :relations_type
t.timestamps
end
end
end
My Relationship model is
class Relationship < ActiveRecord::Base
attr_accessible :relations_id
belongs_to :relations, :polymorphic => true
has_many :followers, :class_name => "User"
end
In my User model
has_many :relationships, :foreign_key => "supporter_id", :dependent => :destroy
and in the other 3 models
has_many :relationships, :as => :relations
Am I missing something with setting up this association?
You basically have it right, except for a few minor errors:
attr_accessible :relations_id is redundant. Remove it from your Relationship model.
Both Relationship and User models call has_many to associate with each other. Relationship should call belongs_to because it contains the foreign key.
In your User model, set :foreign_key => "follower_id".
Here is how I would do it.
Have a Follow middle class with polymorphic association on the followable content side and has_many on the follower user side (user has many follows).
First, create a follows table:
class CreateFollows < ActiveRecord::Migration
def change
create_table :follows do |t|
t.integer :follower_id
t.references :followable, :polymorphic => true
t.timestamps
end
end
end
Replace Relationship model with a Follow model:
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :followers, :class_name => "User"
end
Include in User model:
has_many :follows, :foreign_key => :follower_id
Include in your three followable classes:
has_many :follows, :as => :followable
You can now do this:
TheContent.follows # => [Follow,...] # Useful for counting "N followers"
User.follows # => [Follow,...]
Follow.follower # => User
Follow.followable # => TheContent
After reading the Ruby on Rails guides and a few of the stackoverflow responses to questions about polymorphic association I understand its use and implementation but I have a question about a specific use scenario. I have tags that can be associated with multiple topics, categories, images and other various models (which also have varying tags) but instead of placing the reference fields (foreign_id, foreign_type) within the tags table, I'd prefer to create a separate association table. Is this still possible using :polymorphic => true?
Something like this:
create_table :tags do |t|
t.string :name
t.remove_timestamps
end
create_table :object_tags, :id => false do |t|
t.integer :tag_id
t.references :tagable, :polymorphic => true
t.remove_timestamps
end
If this isn't possible, I was planning on creating the same :object_tags table and using :conditions within the Tag model and other models to force the associations. Is there a rails way of doing this? Thanks! (working with rails 3.0.9 & ruby 1.8.7 <- because deployment server is still using 1.8.7)
UPDATE:
Thanks Delba! Answer is a working solution for HABTM polymorphism.
class Tag < ActiveRecord::Base
has_many :labels
end
class Label < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :tag
end
class Topic < ActiveRecord::Base
has_many :labels, :as => :taggable
has_many :tags, :through => :labels
end
create_table :tags, :timestamps => false do |t|
t.string :name
end
create_table :labels, :timestamps => false, :id => false do |t|
t.integer :tag_id
t.references :taggable, :polymorphic => true
end
UPDATE: Because I need bi-directional HABTM, I ended up going back to creating individual tables.
Yes, and from your description you couldn't have the tagable columns on your tag anyhow since they can have multiple tagable things and vice versa . You mentioned HABT, but you can't do anything like has_and_belongs_to, :polymorphic => true as far as I know.
create_table :object_tags, :id => false do |t|
t.integer :tag_id
t.integer :tagable_id
t.string :tagable_type
end
Your other tables don't need any columns for object_tags, tags, or tagable.
class Tag < ActiveRecord::Base
has_many :object_tags
end
class ObjectTag < ActiveRecord::Base
belongs_to :tagable, :polymorphic => true
belongs_to :tag
end
class Topic < ActiveRecord::Base
has_many :object_tags, :as => :tagable
has_many :tags, :through => :object_tags
end
I have a User model and a Tag model. The User has Skills and Interests.
A Skill is a Tag, and an Interest is a Tag.
I have a table for Users, Tags, UsersSkills, UsersInterests. The last two being the intermediate table. How do I associate all this. The following is what I have but is not working. Thanks ahead of time.
#User model
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
has_and_belongs_to_many :interests
end
#Tag model
class Tag < ActiveRecord::Base
has_and_belongs_to_many :users
end
#Migrations
create_table :users_interests, :id => false do |t|
t.references :user
t.references :tag
end
create_table :users_skills, :id => false do |t|
t.references :user
t.references :tag
end
SO here is the answer for anyone else experiencing this problem. The intermediate table had to have its name be alphabetically in order, even if that means readability goes down the tube. A join_table was then used. If this is not the right answer (it works but might not be good coding), please let me know.
class User < ActiveRecord::Base
has_and_belongs_to_many :skills, :class_name => "Tag", :join_table => "skills_users"
has_and_belongs_to_many :interests, :class_name => "Tag", :join_table => "interests_users"
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :users
end
create_table :skills_users, :id => false do |t|
t.references :user
t.references :tag
end
create_table :interests_users, :id => false do |t|
t.references :user
t.references :tag
end
It's expecting your join tables to have skill_id and interest_id FK's rather than tag_id.
I believe you're looking for (don't have a terminal handy):
class User < ActiveRecord::Base
has_and_belongs_to_many :skills, :association_foreign_key => :tag_id
has_and_belongs_to_many :interests, :association_foreign_key => :tag_id
end