Rails - Delete rows from a non indexed table - sql

I have a connection table that connects between 2 tables. It has 2 columns: user_id and course_id. The name of the table is: course_sub_managers. This table does not have an index. So, how do I delete all rows that meet a condition in which course_id = certain variable? As for now I use:
sql = "DELETE FROM course_sub_managers WHERE course_id = " + #course.id.to_s
ActiveRecord::Base.connection.execute(sql)
Is there a Rails way to write it?

I suspect you have an course model where you have written something like:
has_many :course_sub_managers
has_many :users, through => :course_sub_managers
in that case you can use:
#course.course_sub_managers.delete_all

It sounds you just want to handle the deletion of a course? Use the :dependent => :destroy on your relationships.
has_many :course_sub_managers, :dependent => :destroy
It will automatically remove the related table items when you destroy a course.

Related

Ruby on Rails Active Records issue

The models in question are:
User Followed
The "followeds" schema(or table) has "user_id" that is a reference to "users" table
And it has "followed_id" that is an integer.
What I want to achieve:
Inside my controller I want to select all the users that have an id corresponding to followed_id
Something like
def index
#users = User.all.where(something like => "user.id: followeds.followed_id")
end
Basically comparing the id for users table (user.id) to followeds.followed_id integers and if they match extract the record of the user.
In your User model you should have:
# No idea what's the nature of the relationship between your models, but
# pick one of these two
has_many :followeds, foreign_key: :followed_id
has_one :followed, foreign_key: :followed_id
then in your query call:
User.joins(:followeds) # or :followed if apply

Adding eager loading to custom has_many relationships

Groups and users have has_many through relationships on the table user_roles, which specifies what role users have in each group (pending, member, admin, etc). For a given group, how might I return all users matching a particular role in that group such that the code is DRY and eager loads the appropriate associations?
class Group < ActiveRecord::Base
has_many :user_roles, dependent: :destroy
has_many :users, through: :user_roles
def members(role)
self.users.includes(:user_roles).where("user_role.role = ?", role)
# Returns following error message:
# PG::UndefinedTable: ERROR: missing FROM-clause entry for table "user_role"
end
Your table name probably 'user_roles' and not 'user_role'. Change the name of table in your where clause to 'user_roles'.
def members(role)
self.users.includes(:user_roles).where("user_roles.role = ?", role)
end

Sort records based on number of rows in another table - Rails

So I have a users table and in my relationship, I have defined that a user has many submissions and submissions belong to a user. I want to sort the users table based on how many submissions they have.
submission model
class Submission < ActiveRecord::Base
belongs_to :user
end
user model
class User < ActiveRecord::Base
has_many :submissions, dependent: :destroy
end
The far I have gone is I'm able to get how many submissions a user has using this query
Submission.all.count(:group => "user_id")
With this for example I'm able to get the number of submissions a user with a specific id has
{1=>3, 2=>5}
I want to have a sorted users table with the user with the highest number of submissions first. How can this be achieved in rails activerecord?
You can do what you want in 2 ways:
Using join, group by and order by count
User.select("COUNT(*) AS count_all, submissions.user_id AS submissions_user_id")
.joins('LEFT JOIN submissions ON submissions.user_id = users.id')
.group('submissions.user_id')
.order('COUNT(submissions.user_id) DESC')
This will generate the following sql:
SELECT COUNT(*) AS count_all, submissions.user_id AS submissions_user_id FROM "users" LEFT JOIN submissions ON submissions.user_id = users.id GROUP BY submissions.user_id ORDER BY COUNT(submissions.id) DESC
LEFT JOIN will get the users with 0 submissions too (if you have that situation)
Using counter_cache
The most efficient solutions for querying, in this context, is to use counter_cache
This will enable you to run a query like this:
User.order('submissions_count DESC')
which translates to:
SELECT * FROM users ORDER BY submissions_count DESC
!!! If you want to implement this, especially in production, do a backup of your database before starting. !!!
Read counter_cache docs to understand what it is and how it can help you.
Add a new column on users table named submissions_count.
class AddSubmissionsCountToUsers < ActiveRecord::Migration
def change
add_column :users, :submissions_count, :integer, default: 0
add_index :users, :submissions_count
end
end
Modify your Submission model and add counter_cache.
class Submission < ActiveRecord::Base
belongs_to :user, counter_cache: true
end
If you have a production database update submissions_count to reflect the number of existing submissions:
User.find_in_batches do |group|
group.each do |user|
user_submissions_count = Submission.where(user_id: user.id).count // find how many subscription a user has
user.update_column(:submissions_count, user_submissions_count)
end
end
Every time a user will create/destroy a subscription, submissions_count will be incremented/decremented for that user to reflect the change.

How to represent interchangeable columns

I'm not quite sure how to phrase this, but is there a good way to implement a table where the columns are essentially interchangeable?
Example: you have a Users model and want to allow two Users to be 'friends'. The obvious way, to me, would be to have a table containing two columns ('friend1' and 'friend2') that each containing the key to a User. This makes it awkward for saying something like "are user1 and user2 friends" because you have to check for "(friend1=user1 AND friend2=user2) OR (friend1=user2 AND friend2=user1)". It would work, but it just seems awkward to me that every time you want to get something from that table you're looking in both columns. Is there a more elegant way do this?
A key choice when making a friendship relationship, is deciding if it is bi-directional. Twitter following being an example of one directional friendship and Facebook friendships being bi-directional. Sounds like you're committed to the bi-directional, so the 2 options you have are:
1) Check both directions
select *
from friendships
where (friend1 = 123 and friend2 = 456) OR (friend2 = 123 and friend1 = 456)
2) Always put the lower user_id into friend1 and the higher user_id into friend2, then your test only needs to check one direction. This is a little trickier to maintain, so I'd only do it needed for perf reasons.
The way that you can implement this might seem a bit awkward. The idea is to have a "friendshipId" in a table with two columns: friendshipId and user. Now the users are interchangeable.
To find out if user1 and user2 are friends:
select friendshipId
from friends
group by friendshipId
having sum(case when name = user1 then 1 else 0 end) > 0 and
sum(case when name = user2 then 1 else 0 end) > 0
Judicious use of constraints, triggers, and stored procedures will ensure that a friend relationship has only two users, that someone cannot friend themselves, and so on.
You can do a has_many through or has_and_belongs_to_many
http://guides.rubyonrails.org/association_basics.html
any how you want a join table that links your user models.
for example
class User < ActiveRecord::Base
has_many :followings
has_many :followers, :through => :followings, :class_name => "User"
has_many :followees, :through => :followings, :class_name => "User"
end
class Following < ActiveRecord::Base
# fields: follower_id followee_id (person being followed)
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
same as user has many :users, or must I use another way for a friend based social network?

Active Record optimization: .includes() generates a very slow DISTINCT query, how can I avoid it?

I've got two relations as follows:
class CfThread < ActiveRecord::Base
self.primary_key = 'thread_id'
self.table_name = 'cforum.threads'
belongs_to :forum, class_name: 'CfForum', :foreign_key => :forum_id
has_many :messages, class_name: 'CfMessage', :foreign_key => :thread_id
attr_accessible :thread_id, :tid, :slug, :forum_id, :archived, :created_at, :updated_at
end
class CfMessage < ActiveRecord::Base
has_one :owner, class_name: 'CfUser', :foreign_key => :user_id
has_many :flags, class_name: 'CfFlag'
belongs_to :thread, class_name: 'CfThread', :foreign_key => :thread_id
attr_accessible :message_id, :mid, :thread_id, :subject, :content,
:author, :email, :homepage, :deleted, :user_id, :parent_id,
:updated_at, :created_at
end
I want to query the last 10 threads and its messages without producing a n+1 queries situation:
#threads = CfThread.includes(:messages).order('cforum.threads.created_at DESC').limit(10)
This produces a really slow query using DISTINCT:
SELECT DISTINCT "cforum"."threads".thread_id, cforum.threads.created_at AS alias_0 FROM "cforum"."threads" LEFT OUTER JOIN "cforum"."messages" ON "cforum"."messages"."thread_id" = "cforum"."threads"."thread_id" ORDER BY cforum.threads.created_at DESC LIMIT 10
The query planner want's to do a full table scan on threads AND messages.
With plain SQL it would be very easy to optimize this situation, but is there a way using AR, too? Can I avoid the DISTINCT without producing n+1 queries?
Greetings,
CK
EDIT: Indexes are given as follows:
Threads:
"threads_pkey" PRIMARY KEY, btree (thread_id)
"index_cforum.threads_on_slug" UNIQUE, btree (slug)
"index_cforum.threads_on_archived" btree (archived)
"index_cforum.threads_on_tid" btree (tid)
"threads_created_at_idx" btree (created_at, updated_at)
Messages:
"messages_pkey" PRIMARY KEY, btree (message_id)
"index_cforum.messages_on_mid" btree (mid)
"index_cforum.messages_on_thread_id" btree (thread_id)
I think your order is confusing active record slightly - I think active record thinks it needs the joined columns in order for the order to work. So it's using its join based include variant, which in turn requires a distinct query when you want to limit it.
Obviously the order depends only on the CfThread model, it's just the AR heuristics that are confused. One way would be to force the eager load mode used by doing
CfThread.preload(:messages).order('cforum.threads.created_at DESC').limit(10)