Setting up a polymorphic association
You basically have it right, except for a few minor errors:
attr_accessible :relations_id
is redundant. Remove it from yourRelationship
model.Both
Relationship
andUser
models callhas_many
to associate with each other.Relationship
should callbelongs_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
How to setup a polymorphic association of People and Users?
I agree with moo-juice, the correct approach would be people + roles. here's a concrete example:
class Person < ActiveRecord::Base
has_many :roles, :through => :roles_people
#columns would be username, password, etc
end
class Role < ActiveRecord::Base
has_many :people, :through > :roles_people
#a column of role_type would be required here and the values of such would be Customer, User, etc. in this class you will put logic that is needed to execute the functions of the role
end
class RolesPerson < ActiveRecord::Base
belongs_to :person
belongs_to :role
#this is a join model for a many-to-many relationship. you can store info in here about when a person acquired a certain role, etc.
end
Setting up polymorphic associations in db when the super is a FK of subclasses?
I opted for the simplest solution: using a polymorphic association (as shown in the second block). It will have better performance and be more maintainable.
Why does a polymorphic association in Rails with source_type set result in the wrong SQL statement?
I found a solution that works around the suspected bug: There is an undocumented method called polymorphic_name
that ActiveRecord uses to determine what model name to use when doing polymorphic lookups.
When I change the Organization model to:
class Organization < ApplicationRecord
self.table_name = 'organisations'
has_many :roles, as: :access_to
has_many :users, through: :roles
def self.polymorphic_name
"Organisation"
end
end
then Organization.first.users
generates the SQL I want:
SELECT "users".* FROM "users" INNER JOIN "roles"
ON "users"."id" = "roles"."user_id"
WHERE "roles"."access_to_id" = ?
AND "roles"."access_to_type" = ?
LIMIT ? [
["access_to_id", 1],
["access_to_type", "Organisation"],
["LIMIT", 11]]
Commit that fixed my example: https://github.com/RSpace/polymorphic-issue/commit/648de2c4afe54a1e1dff767c7b980bb905e50bad
I'd still love to hear why the other approach doesn't work though. This workaround seems risky, as I simply discovered this method by digging through the Rails code base, and it's only used internally: https://github.com/rails/rails/search?q=polymorphic_name&unscoped_q=polymorphic_name
EDIT: I now understand why setting source_type: "Organisation"
results in a lookup in the organisations
table rather than the users
table, as the source_type
option controls both model, table and polymorphic name as per the documentation. There is still a bug around getting "access_to_type" set twice, but fixing that won't get my use case working, as source_type
is first and foremost for controlling, well, the source type of the association. I will instead pursue to get the polymorphic_name
method documented and thus be part of the official ActiveRecord API.
Setting up a polymorphic association where associated tables have different primary key types (UUID and Integer)
The solution I ended up with is adding a likeable_id_string
which stores any uuid
primary key. I then override the getters and setters to do the following:
# Overriding getters and setters to handle multiple primary key types on
# this polymorphic association ( String and Integer )
# Setters
def likeable=(value)
if !value.nil? && value.id.is_a?(String)
self[:likeable_id] = nil
self[:likeable_id_string] = value.id
self[:likeable_type] = value.class.name
else
self[:likeable_id_string] = nil
super(value)
end
end
# in case likeable_type and likeable_id are updated seperately. I believe
# this is done in FE (rather than passing an object to .likeable)
def likeable_id=(value)
if !value.nil? && value.is_a?(String)
self[:likeable_id] = nil
self[:likeable_id_string] = value
else
self[:likeable_id_string] = nil
super(value)
end
end
# Getters
def likeable
if self.likeable_id
return super
elsif !self.likeable_type.nil?
return self.likeable_type.constantize.find(likeable_id_string)
else
return nil
end
end
You would think that the likeable=
setter isn't needed once I've overridden the likeable_id=
.. but seems like it is. If anyone knows why I need both please let me know as it would seem logical that likeable
delegates to likeable_id
and likeable_type
I feel like there's a use case for this to be a Gem. Something I might do soon. Please get in touch if you would like to see this as a gem (or if you think that's a terrible idea)
How to create seeds of a polymorphic relationship
to associate an image with both a seed employee and seed product
This would require a many-to-many
relationship (either has_and_belongs_to_many
or has_many :through
):
#app/models/product.rb
class Product < ActiveRecord::Base
has_many :images, as: :imageable
has_many :pictures, through: :images
end
#app/models/employee.rb
class Employee < ActiveRecord::Base
has_many :images, as: :imageable
has_many :pictures, through: :images
end
#app/models/image.rb
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
belongs_to :picture
end
#app/models/picture.rb
class Picture < ActiveRecord::Base
has_many :images
end
This would allow you to use:
#db/seed.rb
@employee = Employee.find_or_create_by x: "y"
@picture = @employee.pictures.find_or_create_by file: x
@product = Product.find_or_create_by x: "y"
@product.pictures << @picture
ActiveRecord, has_many :through, and Polymorphic Associations
Because you're using a polymorphic
relationship, you won't be able to use has_and_belongs_to_many
.
The above will set the polymorphism on the join
table; each Picture
being "naked" (without a "creator"). Some hacking would be required to define the original creator of the image.
Polymorphic association, creating a record
I fixed this issue by doing this:
new_item = GroupItem.new(group_id: group.id)
new_item.update_attribute(:groupable, person)
irb(main):046:0> new_item = GroupItem.new(group_id: group.id)
=> #<GroupItem id: nil, group_id: 1, groupable_type: nil, groupable_id: nil, created_at: nil, updated_at: nil>
irb(main):047:0> new_item.update_attribute(:groupable, person)
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "group_items" ("group_id", "groupable_type", "groupable_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["group_id", 1], ["groupable_type", "Role"], ["groupable_id", 29], ["created_at", 2017-02-23 03:14:33 UTC], ["updated_at", 2017-02-23 03:14:33 UTC]]
(1.6ms) commit transaction
=> true
irb(main):049:0> new_item
=> #<GroupItem id: 3, group_id: 1, groupable_type: "Role", groupable_id: 29, created_at: "2017-02-23 03:14:33", updated_at: "2017-02-23 03:14:33">
Setting up a polymorphic has_many :through relationship
To create a polymorphic has_many :through, you must first create your models. We will use'Article,' 'Category,' and 'Tag' where 'Tag' is the join-model and Article is one of many objects which can be "tagged" with a category.
First you create your 'Article' and 'Category' models. These are basic models which do not need any special attention, just yet:
rails g model Article name:string
rails g model Category name:string
Now, we will create our polymorphic join-table:
rails g model Tag taggable_id:integer taggable_type:string category_id:integer
The join-table joins together two tables, or in our case one table to many others via polymorphic behavior. It does this by storing the ID from two separate tables. This creates a link. Our 'Category' table will always be a 'Category' so we include 'category_id.' The tables it links to vary, so we add an item 'taggable_id' which holds the id of any taggable item. Then, we use 'taggable_type' to complete the link allowing the link to know what it is linked to, such as an article.
Now, we need to set up our models:
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable, :dependent => :destroy
has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
has_many :tags, :dependent => :destroy
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
After this, setup your database using:
rake db:migrate
That's it! Now, you can setup your database with real data:
Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."
Now you have a few categories and various articles. They are not categorized using tags, however. So, we will need to do that:
a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save
You could then repeat this for each, this will link your categories and articles. After doing this you will be able to access each article's categories and each categorie's articles:
Article.first.categories
Category.first.articles
Notes:
1)Whenever you want to delete an item that is linked by a link-model make sure to use "destroy." When you destroy a linked object, it will also destroy the link. This ensures that there are no bad or dead links. This is why we use ':dependent => :destroy'
2)When setting up our 'Article' model, which is one our 'taggable' models, it must be linked using :as. Since in the preceeding example we used 'taggable_type' and 'taggable_id' we use :as => :taggable. This helps rails know how to store the values in the database.
3)When linking categories to articles, we use:
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
This tells the category model that it should have many :articles through :tags. The source is :taggable, for the same reason as above. The source-type is "Article" because a model will automatically set taggable_type to its own name.
Related Topics
Ruby: Append Text to the 2Nd Line of a File
Error Occurs When Trying to Install Homebrew on a MAC for Ruby on Rails
Nomethoderror on Section 5.7 of Rails Guide
How to Merge Two Equally Sized Arrays into One Array with Sub-Arrays of Merged Values
Ruby Net-Ssh Calling Bash Script with Interactive Prompts
Anyone Can Comment This Ruby Code
Rails: Logging the Entire Stack Trace of an Exception
Should I Specify Exact Versions in My Gemfile
Why Does the Script Affect Everything on My Rails 3 App Even When Cased in This Code
Rails: How to Use Scope to Find an Element in Array of Arrays
Rails Contact Form Not Working
How to Prevent My Users to Read My Ruby Code