Rails intermediate table assosciations - ruby-on-rails

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

Related

Ruby on Rails: has_and_belongs_to_many connection to inherited subclass

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

counter_cache not updating on has_one association

I have an Item with a has_one relationship to its canonical_item_id. This should return the canonical_item when present. This is working fine.
has_one :canonical_item, class_name: "Item", foreign_key: :id, primary_key: :canonical_id
belongs_to :canonical_item, counter_cache: true
I try to set belongs_to :canonical_item, counter_cache: true to update the canonical_item.items_counter counter but it is not updating. I also notice that when adding I lost the association to the cannonical_item
Item.last.canonical_item
=> nil
Any idea?
I wouldn't be surprised if this is because of methods and/or callbacks for your has_one and belongs_to associations overriding each other. It seems an unlikely situation that you need both, though.
If what you need is a belongs_to association:
create_table :items do |t|
t.integer :canonical_item_id
end
create_table :canonical_items do |t|
t.integer :items_count, :default => 0
end
class Item < ActiveRecord::Base
belongs_to :canonical_items, :counter_cache => true
end
class CanonicalItem < ActiveRecord::Base
has_many :items, :dependent => :nullify
end
If what you need is a has_one association, the DB structure as it is wouldn't allow more than one Item for a CanonicalItem, so it doesn't make much sense to count associated Item records. You can simply check if there is a value for :item_id.
create_table :items do |t|
end
create_table :canonical_items do |t|
t.integer :item_id
end
class Item < ActiveRecord::Base
has_one :canonical_item, :dependent => :nullify
end
class CanonicalItem < ActiveRecord::Base
belongs_to :item
end
The field for cache counters is items_count not items_counter.
Make sure that field exists in your database.

Setting up a polymorphic association

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

Separate Polymorphic Association table

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

Polymorphic association using namespaced classes

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'.

Resources