Association for the child of the child in ruby on rails - sql

Example of data in User table
Expected result in rails console, grandfather.grandchildren and grandmother.grandchildren should return the same group of objects:
grandfather = User.first
grandfather.grandchildren
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 5, name: "father's son", father_id: 3, mother_id: nil>, #<User id: 6, name: "uncle's son", father_id: 4, mother_id: nil>]>
grandmother = User.find(2)
grandmother.grandchildren
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 5, name: "father's son", father_id: 3, mother_id: nil>, #<User id: 6, name: "uncle's son", father_id: 4, mother_id: nil>]>
This is my association now in User.rb model.
has_many :children, ->(user) { unscope(:where).where("father_id = :id OR mother_id = :id", id: user.id) }, class_name: "User"
has_many :grandchildren, through: :children, source: :children
belongs_to :mother, class_name: "User", optional: true
belongs_to :father, class_name: "User", optional: true
Output in rails console now:
irb(main):001:0> grandfather = User.first
(0.3ms) SELECT sqlite_version(*)
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "grandfather", mother_id: nil, father_id: nil>
irb(main):002:0> grandfather.grandchildren
User Load (0.3ms) SELECT "users".* FROM "users" INNER JOIN "users" "children_grandchildren" ON "users"."user_id" = "children_grandchildren"."id" WHERE (father_id = 1 OR mother_id = 1) /* loading for inspect */ LIMIT ? [["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (SQLite3::SQLException: ambiguous column name: father_id)

You can't get the grandchildren from a grandparent by going through its children because it implies that their father/mother ids are equal to the grandparent, it doesn't travel through the grandchildren parents:
SELECT "users".*
FROM "users"
INNER JOIN "users" "children_grandchildren"
ON "users"."user_id" = "children_grandchildren"."id"
WHERE (father_id = 1 OR mother_id = 1) -- this is the grandparent id, when it should be the child parent's id
You can add a callable to the grandchildren relationship, similar to the one for children, but this time extracting the grandparent children ids, and using the IN clause to filter those user rows matching those ids, with their father/mother ids:
has_many :grandchildren,
->(user) { unscope(:where).where('father_id IN (:ids) OR mother_id IN (:ids)', ids: user.children.ids) },
class_name: "User"

Related

How to list all posts with favorites status that is implemented by many to many relations

I have three models User, Post and Favorite.
class User < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
end
class Favorite < ApplicationRecord
belongs_to :post
belongs_to :user
end
> user = User.create(name: 'user1')
> user.posts.create(title: 'Title 1')
> user.posts.create(title: 'Title 2')
> Favorite.create(user_id: 1, post_id: 2)
I want to retrieve all posts belongs to user1 with fav status by the user.
> User.first.posts_with_fav_status
=> [#<Post id: 1, title: "Title 1", user_id: 1, faved: false> ],
[#<Post id: 2, title: "Title 2", user_id: 1, faved: true> ]
How can I write the query method like this?
Edit
I could get fav status with the following query. But this query calls subquery every time. It will be too slow when DB become bigger. How can I rewrite this query?
def posts_with_fav_status
posts.select(<<-SQL)
*,
EXISTS(SELECT * FROM favorites
WHERE favorites.user_id = posts.user_id
AND favorites.post_id = posts.id) as faved
SQL
end
I came to this solution (tested on Rails 6.1.4.4)
class User < ApplicationRecord
has_many :posts
# User.first.posts_with_faved
def posts_with_faved
posts.select("posts.*, favorites.user_id = #{id} as faved")
.left_joins(:favorites)
end
end
# due to how the inspect works, you won't see the faved attribute in the output, but it is there
irb(main):053:0> first, second = User.first.posts_with_faved
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Post Load (0.2ms) SELECT posts.*, favorites.user_id = 1 as faved FROM "posts" LEFT OUTER JOIN "favorites" ON "favorites"."post_id" = "posts"."id" WHERE "posts"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::AssociationRelation [#<Post id: 1, title: "Title 1", user_id: 1, created_at: "2022-01-28 09:34:19.048598000 +0000", updated_at: "2022-01-28 09:34:19.048598000 +0000">, #<Post id: 2, title: "Title 2", user_id: 1, created_at: "2022-01-28 09:34:22.172245000 +0000", ...
irb(main):054:0> first.faved
=> nil
irb(main):055:0> second.faved
=> 1
Things to take into account:
be careful and not override with select if you chain the query. Otherwise everything will break
The query does not return a boolean, but nearly: nil instead of false and 1 instead of true. This will still work if you use it with an if
You miss relationships in User and Post model:
User:
class User < ApplicationRecord
has_many :posts
has_many :favorites
has_many :favorite_posts, through: :favorites, source: :post
end
Post:
class Post < ApplicationRecord
belongs_to :user
has_many :favorites
has_many :favorited_by, through: :favorites, source: :user
end
With that you can write
u = User.first
u.favorite_posts.where(user_id: u.id)
The way you have your tables and models I don't see a way to avoid that subquery to get the result you want.
Not sure what your use case is, but I'm assuming these are posts users are saving, and some will be favorites, because if the user is the author of the post and they are favoriting their own posts, then why not just add a column to the posts table designating it as a favorite?
If my assumption is correct, it might work better to ditch the Favorite model and use a UserPosts loookup model instead that includes the user_id, post_id, favorite.
Class User < ApplicationRecord
has_many :user_posts
has_many :posts, through: :user_posts
end
Class UserPost < ApplicationRecord
belongs_to :user
belongs_to :post
end
Class Post < ApplicationRecord
has_many :user_posts
has_many :users, through: :user_posts
end
Then you can use
user = User.first
user_posts = user.user_posts.eager_load(:posts)
The fave is on the user_posts table/model so it could be accessed by user_posts.first.fave? and the actual post content user_posts.first.post.
You can add accepts_nested_attributes_for :post to the UserPost model and to create a new post association for a user:
User.first.user_posts.create(fave: true, post: {title: 'title text', ...})
Other than something like that, you're going to have to use a subquery or second query and remap into a hash/array of your own design afterwards.

How to form a rails query to grab all the discussion posts a particular user has made?

I am using rails 5.0.0
I want to make a query that will let me display all the posts the current user has made. The two relevant tables I have are:
create_table "discussions", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "channel_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
....
t.string "unconfirmed_email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
end
create_table "replies", force: :cascade do |t|
t.text "reply"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "discussion_id"
t.integer "user_id"
end
and the relationships are as follows:
class Discussion < ApplicationRecord
belongs_to :channel
belongs_to :user
has_many :replies, dependent: :destroy
has_many :users, through: :replies
class Reply < ApplicationRecord
belongs_to :discussion
belongs_to :user
class User < ApplicationRecord
rolify
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable
has_many :notifications, foreign_key: :recipient_id
has_many :discussions, dependent: :destroy
has_many :channels, through: :discussions
in my discussions_controller.rb file i have the following line
#discussions = Discussion.includes(:users).where('users.id' => current_user).order('discussions.created_at desc')
and in my view file I have
<% #discussions.each do |discussion| %>
...
<% end %>
I expect there to be a few entries since I have created them, however no entries are displayed at all. This is what is printed in the terminal window
Processing by DiscussionsController#index as HTML
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
Rendering discussions/index.html.erb within layouts/application
SQL (0.5ms) SELECT "discussions"."id" AS t0_r0, "discussions"."title" AS t0_r1, "discussions"."content" AS t0_r2, "discussions"."created_at" AS t0_r3, "discussions"."updated_at" AS t0_r4, "discussions"."user_id" AS t0_r5, "discussions"."channel_id" AS t0_r6, "users"."id" AS t1_r0, "users"."email" AS t1_r1, "users"."encrypted_password" AS t1_r2, "users"."reset_password_token" AS t1_r3, "users"."reset_password_sent_at" AS t1_r4, "users"."remember_created_at" AS t1_r5, "users"."sign_in_count" AS t1_r6, "users"."current_sign_in_at" AS t1_r7, "users"."last_sign_in_at" AS t1_r8, "users"."current_sign_in_ip" AS t1_r9, "users"."last_sign_in_ip" AS t1_r10, "users"."confirmation_token" AS t1_r11, "users"."confirmed_at" AS t1_r12, "users"."confirmation_sent_at" AS t1_r13, "users"."unconfirmed_email" AS t1_r14, "users"."created_at" AS t1_r15, "users"."updated_at" AS t1_r16, "users"."username" AS t1_r17 FROM "discussions" LEFT OUTER JOIN "replies" ON "replies"."discussion_id" = "discussions"."id" LEFT OUTER JOIN "users" ON "users"."id" = "replies"."user_id" WHERE "users"."id" = 1 ORDER BY discussions.created_at desc
Rendered shared/_discussions.html.erb (8.5ms)
Channel Load (0.3ms) SELECT "channels".* FROM "channels" ORDER BY created_at desc
Role Load (0.3ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = ? AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 1]]
Rendered discussions/_sidebar.html.erb (34.1ms)
Rendered discussions/index.html.erb within layouts/application (46.1ms)
Completed 200 OK in 245ms (Views: 165.7ms | ActiveRecord: 6.7ms)
If I use
#discussions = Discussion.includes(:users).order('discussions.created_at desc')
Then all the discussion posts display as they normally would as if the .includes statement was not there.
So, how can I change my query to list out the discussions made by the current user?
Update As per Shiko's comment, here is the output given with his input in the rails console
2.3.0 :001 > User.find(1).discussions
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Discussion Load (0.5ms) SELECT "discussions".* FROM "discussions" WHERE "discussions"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Discussion id: 1, title: "Test", content: "alkjdflk slkfj ", created_at: "2018-04-08 22:40:06", updated_at: "2018-04-08 22:40:06", user_id: 1, channel_id: nil>, #<Discussion id: 2, title: "Fake Bakesale", content: "Come buy cookies", created_at: "2018-04-08 23:29:17", updated_at: "2018-04-08 23:29:17", user_id: 1, channel_id: 1>, #<Discussion id: 3, title: "Fake Bakesale", content: "Come buy cookies", created_at: "2018-04-08 23:30:18", updated_at: "2018-04-08 23:30:18", user_id: 1, channel_id: 1>, #<Discussion id: 4, title: "Meeting today", content: "Come to the meeting", created_at: "2018-04-08 23:35:59", updated_at: "2018-04-08 23:35:59", user_id: 1, channel_id: 1>, #<Discussion id: 5, title: "New post", content: "asdf ", created_at: "2018-04-15 21:50:20", updated_at: "2018-04-15 21:50:20", user_id: 1, channel_id: 2>]>
2.3.0 :002 >
First thing, if you run below command in rails console, you should get an below expected error :
#discussions = Discussion.includes(:users).where('users.id' => current_user).order('discussions.created_at desc')
Expected error:
ActiveRecord::ConfigurationError: Can't join 'Discussion' to
association named 'users'; perhaps you misspelled it?
To fix this issue, you have to use :user instead of :users as below, simply because each discussion belongs to one user and not many :
#discussions = Discussion.includes(:user).where('users.id' => current_user).order('discussions.created_at desc')
There is a more a clean one way to get the current user discussions, using below:
Controller:
#user = User.find(curren_user_id)
ERB file:
<% #user.discussions.each do |discussion| %>
.....
<% end %>
You can do this:
#discussions = Discussion.includes(:user).
where(users: { id: current_user.id }).
order("discussions.created_at desc")
If you don't need to reference the user attributes in your view, you can also do this, which avoids the join altogether:
#discussions = Discussion.
where(user_id: current_user.id).order(created_at: :desc)

Adding select to model removes .count functionality (rails)

When creating a join table as follows
class Project < ActiveRecord::Base
belongs_to :user
has_many :time_pledges, dependent: :destroy
has_many :volunteers, through: :time_pledges
class User < ActiveRecord::Base
has_many :projects, dependent: :destroy
has_many :time_pledges, foreign_key: "volunteer_id", dependent: :destroy
has_many :volunteering, through: :time_pledges, source: :project
class TimePledge < ActiveRecord::Base
belongs_to :volunteer, class_name: "User"
belongs_to :project
I am able to do the following #project.volunteers.count and get an answer for how many users are volunteering for a specific project at that time. However when I update the project model to be able to access the hours_pledged attribute of the join table model (time_pledges) as follows:
has_many :volunteers, -> { select('users.*, time_pledges.hours_pledged as hours_pledged')}, through: :time_pledges
I am no longer able to access #project.volunteers.count. The error I get is as follows
P1.volunteers
User Load (1.6ms) SELECT users.*, time_pledges.hours_pledged as hours_pledged FROM "users" INNER JOIN "time_pledges" ON "users"."id" = "time_pledges"."volunteer_id" WHERE "time_pledges"."project_id" = $1 [["project_id", 300]]
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 1, email: "durham#example.com", encrypted_password: "$2a$10$4fyCd4GGtwZ0NRzrJuPDd.KWAhXWWumJ1LqtqZOSYWQ...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2015-03-06 08:59:06", updated_at: "2015-03-06 08:59:06">]>
>> P1.volunteers.count
PG::SyntaxError: ERROR: syntax error at or near "as"
LINE 1: SELECT COUNT(users.*, time_pledges.hours_pledged as hours_pl...
^
: SELECT COUNT(users.*, time_pledges.hours_pledged as hours_pledged) FROM "users" INNER JOIN "time_pledges" ON "users"."id" = "time_pledges"."volunteer_id" WHERE "time_pledges"."project_id" = $1
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "as"
LINE 1: SELECT COUNT(users.*, time_pledges.hours_pledged as hours_pl...
^
: SELECT COUNT(users.*, time_pledges.hours_pledged as hours_pledged) FROM "users" INNER JOIN "time_pledges" ON "users"."id" = "time_pledges"."volunteer_id" WHERE "time_pledges"."project_id" = $1
Also after I try to do a #project.volunteers.count I am no longer able to access any of my Users again (until I restart a console session). For example if I do something like U5=User.find_by(id:5) after a #project.volunteers.count I get the following message:
User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 5]]
PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block
: SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1
ActiveRecord::StatementInvalid: PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block
: SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1
Any help is appreciated
Thanks
D
EDIT 1
Schema
create_table "time_pledges", force: :cascade do |t|
t.integer "volunteer_id"
t.integer "project_id"
t.integer "hours_pledged"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "time_pledges", ["project_id"], name: "index_time_pledges_on_project_id", using: :btree
add_index "time_pledges", ["volunteer_id", "project_id"], name: "index_time_pledges_on_volunteer_id_and_project_id", unique: true, using: :btree
add_index "time_pledges", ["volunteer_id"], name: "index_time_pledges_on_volunteer_id", using: :btree
Using times_pledges instead of time_pledges
>> P1=Project.first Project Load (1.7ms) SELECT "projects".* FROM "projects" ORDER BY "projects"."created_at" DESC LIMIT 1 => #<Project id: 301, title: "awe", user_id: 101, created_at: "2015-03-06 15:19:17", updated_at: "2015-03-06 15:19:17", required_hours: 7> >> P1.volunteers NameError: uninitialized constant Project::TimesPledge
Using
has_many :volunteers, -> { select('users.*, time_pledges.hours_pledged hours_pledged')}, through: :time_pledges
gives
>> P1.volunteers
User Load (1.4ms) SELECT users.*, time_pledges.hours_pledged hours_pledged FROM "users" INNER JOIN "time_pledges" ON "users"."id" = "time_pledges"."volunteer_id" WHERE "time_pledges"."project_id" = $1 [["project_id", 301]]
=> #<ActiveRecord::Associations::CollectionProxy []>
>> P1.volunteers.count
PG::SyntaxError: ERROR: syntax error at or near "hours_pledged"
LINE 1: SELECT COUNT(users.*, time_pledges.hours_pledged hours_pledg...
^
: SELECT COUNT(users.*, time_pledges.hours_pledged hours_pledged) FROM "users" INNER JOIN "time_pledges" ON "users"."id" = "time_pledges"."volunteer_id" WHERE "time_pledges"."project_id" = $1
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "hours_pledged"
LINE 1: SELECT COUNT(users.*, time_pledges.hours_pledged hours_pledg...
^
: SELECT COUNT(users.*, time_pledges.hours_pledged hours_pledged) FROM "users" INNER JOIN "time_pledges" ON "users"."id" = "time_pledges"."volunteer_id" WHERE "time_pledges"."project_id" = $1
In using Postgresql I don't use AS unless it's for a select statement that I am joining to i.e.
SELECT parts.*, alias_skus
FROM parts
LEFT JOIN (
SELECT part_id, array_agg(sku) alias_skus
FROM part_aliases GROUP BY part_id)
AS sub1
ON parts.id = sub1.part_id;
For column aliases, just leave out the 'AS'. You can see here I'm using the alias alias_skus.
thus:
has_many :volunteers, -> { select('users.*, time_pledges.hours_pledged hours_pledged')}, through: :time_pledges

scope for count children has_many relation

I have two models
# == Schema Information
#
# Table name: answers
#
# id :integer not null, primary key
# content :text
# question_id :integer
# accept :boolean
# created_at :datetime not null
# updated_at :datetime not null
# user_id :integer
#
class Answer < ActiveRecord::Base
attr_accessible :accept, :content, :question_id, :user
belongs_to :question
belongs_to :user
delegate :username, to: :user, allow_nil: true, prefix: 'owner'
end
and Question
# == Schema Information
#
# Table name: questions
#
# id :integer not null, primary key
# title :string(255)
# content :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :integer
# viewed_count :integer default(0)
#
class Question < ActiveRecord::Base
validates_presence_of :title, :content, :user
attr_accessible :content, :title, :tag_list
acts_as_taggable
belongs_to :user, :counter_cache => true
has_many :answers
delegate :username, to: :user, allow_nil: true, prefix: 'owner'
scope :owner, joins(:user)
scope :without_answer, joins(:answers).
select('questions.id').
group('questions.id').
having('count(answers.id) = 0')
validate :validation_of_tag_list
def self.no_answer
Question.all.select{|question|question.answers.count == 0}
end
The scope without_answer and class method no_answer theoretically should be same. However, I run them in the console as below:
Loading development environment (Rails 3.2.13)
irb(main):001:0> Question.without_answer
Question Load (0.6ms) SELECT questions.id FROM `questions` INNER JOIN `answers` ON `answers`.`question_id` = `questions`.`id` GROUP BY questions.id HAVING count(answers.id) = 0
=> []
irb(main):002:0> Question.no_answer
Question Load (0.6ms) SELECT `questions`.* FROM `questions`
(0.5ms) SELECT COUNT(*) FROM `answers` WHERE `answers`.`question_id` = 1
(0.4ms) SELECT COUNT(*) FROM `answers` WHERE `answers`.`question_id` = 2
(0.4ms) SELECT COUNT(*) FROM `answers` WHERE `answers`.`question_id` = 16
(0.4ms) SELECT COUNT(*) FROM `answers` WHERE `answers`.`question_id` = 17
(0.3ms) SELECT COUNT(*) FROM `answers` WHERE `answers`.`question_id` = 34
=> [#<Question id: 2, title: "Here is the second", content: "here you go\r\n", created_at: "2013-04-20 00:34:15", updated_at: "2013-04-20 00:34:15", user_id: nil, viewed_count: 0>, #<Question id: 16, title: "my question", content: "Here is my question", created_at: "2013-04-21 02:02:47", updated_at: "2013-04-23 02:29:27", user_id: 1, viewed_count: 1>, #<Question id: 17, title: "Hello", content: "me", created_at: "2013-04-23 00:37:56", updated_at: "2013-04-23 00:37:56", user_id: nil, viewed_count: 0>, #<Question id: 34, title: "Question title", content: "question content", created_at: "2013-04-23 04:57:49", updated_at: "2013-04-23 04:57:49", user_id: 42, viewed_count: 0>]
why the scope dose not work as expect?
which way will be better to due with such situation or even better solution?
Your without_answer scope is very close, but needs an outer join like this:
scope :without_answer,
joins('LEFT OUTER JOIN answers ON answers.question_id = questions.id').
select('questions.id').
group('questions.id').
having('count(answers.id) = 0')
Then, you can get the count with length:
Question.without_answer.length
Note: if you want without_answer to be the same as no_answer (i.e. return actual Question objects), you would need to remove the select.
A simpler and faster way to count the unanswered questions is like this:
Question.joins('LEFT OUTER JOIN answers ON answers.question_id = questions.id').
where('answers.id' => nil).count
Also, this will return the same as no_answer as-is, simply use all instead of count.

How to retrieve associated objects / records that a set of users have in common through a association table?

I am using Ruby on Rails 3.2.2 and I would like to retrieve objects / records simultaneously associated by two or more users. That is, I have a database table where I store association data between users and articles; I would like to "build" a SQL query so to retrieve associated articles by two or more users. For instance, if I have followings association objects
#<UserArticleAssociation id: 1, user_id: 1, article_id: 1>
#<UserArticleAssociation id: 2, user_id: 1, article_id: 2>
#<UserArticleAssociation id: 3, user_id: 1, article_id: 3>
#<UserArticleAssociation id: 4, user_id: 2, article_id: 1>
#<UserArticleAssociation id: 5, user_id: 2, article_id: 2>
#<UserArticleAssociation id: 6, user_id: 3, article_id: 1>
#<UserArticleAssociation id: 7, user_id: 3, article_id: 3>
#<UserArticleAssociation id: 8, user_id: 4, article_id: 4>
I would to state / run a scope method so to get something like the following:
#user1.articles.associated_by(#user2)
# => [ #<UserArticleAssociation id: 1, user_id: 1, article_id: 1>,
#<UserArticleAssociation id: 4, user_id: 2, article_id: 1>]
#user1.articles.associated_by(#user3)
# => [ #<UserArticleAssociation id: 1, user_id: 1, article_id: 1>,
#<UserArticleAssociation id: 7, user_id: 3, article_id: 3>]
#user1.articles.associated_by(#user4)
# => nil
#user2.articles.associated_by(#user3)
# => [ #<UserArticleAssociation id: 1, user_id: 1, article_id: 1>]
#user1.articles.associated_by([#user2, #user3])
# => [ #<UserArticleAssociation id: 1, user_id: 1, article_id: 1>]
In others words, I would like to find articles that a set of users have in common through the user_article_associations table. How can I make that?
Involved classes are stated as
class User < ActiveRecord::Base
has_many :article_associations, :class_name => 'UserArticleAssociation'
has_many :articles, :through => :article_associations
end
class Article < ActiveRecord::Base
has_many :user_associations
has_many :users, :through => :user_associations
end
You should use having clause with group_by
Article.joins(:user_article_associations).
where('user_article_associations.user_id in (?)', users_ids)).
group('articles.id').
having('COUNT(user_article_associations.user_id in (?)) = ?',users_ids, users_ids.size)
Use merge to combine the relations:
articles = #user1.articles.merge(#user2.articles)
This will get you all Articles that #user1 & #user2 share. You can further call merge with additional relations for Articles, e.g.:
articles = #user1.articles.merge(#user2.articles).merge(#user3.articles)
Maybe something like this will work:
Article.joins(:users).where('users.id in (?)', my_users.map(&:id)).group('articles.id')