4

It seems like rails still not support this type of relation and throws ActiveRecord::HasManyThroughAssociationPolymorphicThroughError error.

What can I do to implement this kind of relation?

I have following associations:

Users 1..n Articles
Categories n..n Articles
Projects 1..n Articles

And here is Subscription model

Subscription 1..1 User
Subscription 1..1 Target (polymorphic (Article, Category or User))

And I need to select articles through Subscription#target#article according to user#subscriptions.

I have no idea hot to implement this

Ideally I want to get instance of Association class

UPDATE 1

Here is little example

Let say user_1 has 4 Subscription records:

s1 = (user_id: 1, target_id: 3, target_type: 'User')
s2 = (user_id: 1, target_id: 2, target_type: 'Category')
s3 = (user_id: 1, target_id: 3, target_type: 'Project')
s4 = (user_id: 1, target_id: 8, target_type: 'Project')

I need method User#feed_articles, that fetches all articles, that belong to any of target, I subscribed.

user_1.feed_articles.order(created_at: :desc).limit(10) 

UPDATE 2

I separate articles sources by type in User model:

  has_many :out_subscriptions, class_name: 'Subscription'

  has_many :followes_users, through: :out_subscriptions, source: :target, source_type: 'User'
  has_many :followes_categories, through: :out_subscriptions, source: :target, source_type: 'Category'
  has_many :followes_projects, through: :out_subscriptions, source: :target, source_type: 'Project'

  has_many :feed_user_articles, class_name: 'Article', through: :followes_users, source: :articles
  has_many :feed_category_articles, class_name: 'Article', through: :followes_categories, source: :articles
  has_many :feed_project_articles, class_name: 'Article', through: :followes_projects, source: :articles

But how can I merge feed_user_articles with feed_category_articles and feed_project_articles without loss of perfomance

UPDATE 3.1

The only way I found is to use raw SQL join query. Looks like it works fine, but I'm not sure.

  def feed_articles
    join_clause = <<JOIN
inner join users on articles.user_id = users.id
inner join articles_categories on articles_categories.article_id = articles.id
inner join categories on categories.id = articles_categories.category_id
inner join subscriptions on
    (subscriptions.target_id = users.id and subscriptions.target_type = 'User') or
    (subscriptions.target_id = categories.id and subscriptions.target_type = 'Category')
JOIN

    Article.joins(join_clause).where('subscriptions.user_id' => id).distinct
  end

(This is just for Users and Categories)

It supports scopes and other features. The only thing interests me: does this query lead to some undesirable effect?

atomAltera
  • 1,702
  • 2
  • 19
  • 38

2 Answers2

0

I think that from DB performance prospective using UNION ALL multiquery will be more efficient than using polymorphic multijoin. Also it will be more readable. I tried to write an Arel query as example but it does not play nice (I failed to make order by clause work properly) so I think you have to put it via raw SQL. You can use SQL template apart from ORDER BY clause for drying it up.

Nikita Shilnikov
  • 1,204
  • 9
  • 7
  • Regarding the order by clause, did you put it at the end of the union? e.g not one order by for each part of the union. This tripped me up the last time i wrote a union sql statement. (nb: last time i wrote that was in oracle SQL, i don't know if the behaviour is different in postgresql og mysql) – Ole Henrik Skogstrøm Jul 16 '14 at 16:33
  • @OleHenrikSkogstrøm yep. According to SQL standard ORDER BY clause must be placed after all unions. But Arel just cannot do so. I recommend split SQL and ORDER BY clause so you can use one SQL with different ordering. – Nikita Shilnikov Jul 16 '14 at 16:53
0

You are correct that Rails doesn't support has_many :through w/ polymorphic associations. You could mimic this behavior by defining an instance method on your User class. That would look something like this:

def articles
  Article.
    joins("join subscriptions on subscriptions.target_id = articles.id and subscriptions.target_type = 'Article'").
    joins("join users on users.id = subscriptions.user_id")
end
Michael Frederick
  • 16,664
  • 3
  • 43
  • 58