Writes a vote directly to avoid vote_thusly doing round trips to check if one
exists, etc.
Removes redundant transactions from controllers from #899. Rails already creates
a transaction for the .save.
Unifies Story cache updating. Previously recalculate_hotness! was called twice
on comment creation. Moves comment counting into the db.
Shorter transaction should reduce the frequence of
lobsters/lobsters-ansible/issues/39 but seems unlikely to eliminate it as the
create + upvote transactions for stories + comments still read/write from
stories, comments, and votes.
I can't take Rubocop any more. Moving up to Ruby 3 forces us to update RuboCop,
and it comes with dozens of linters that I'd have to evaluate, none of which is
a marginal improvement. I'm done having opinions.
diff of `rails routes`:
12,13d11
< GET /newest/:user(.:format) home#newest_by_user
< GET /newest/:user/page/:page(.:format) home#newest_by_user
32d29
< user_threads GET /threads/:user(.:format) comments#user_threads
120,122c117,132
< u GET /u(.:format) users#tree
< user GET /u/:username(.:format) users#show
< user_standing GET /u/:username/standing(.:format) users#standing
---
> users_tree GET /users(.:format) users#tree
> user GET /~:username(.:format) users#show
> user_standing GET /~:username/standing(.:format) users#standing
> GET /~:user/stories(/page/:page)(.:format) home#newest_by_user
> user_threads GET /~:user/threads(.:format) comments#user_threads
> user_ban POST /~:username/ban(.:format) users#ban
> user_unban POST /~:username/unban(.:format) users#unban
> user_disable_invite POST /~:username/disable_invitation(.:format) users#disable_invitation
> user_enable_invite POST /~:username/enable_invitation(.:format) users#enable_invitation
> u GET /u(.:format) redirect(302, /users)
> GET /u/:username(.:format) redirect(302, /~%{username})
> GET /@:username(.:format) redirect(302, /~%{username})
> GET /u/:username/standing(.:format) redirect(302, ~%{username}/standing)
> GET /newest/:user(.:format) redirect(302, ~%{user}/stories)
> GET /newest/:user(/page/:page)(.:format) redirect(302, ~%{user}/stories/page/%{page})
> GET /threads/:user(.:format) redirect(302, ~%{user}/threads)
125,128d134
< user_ban POST /users/:username/ban(.:format) users#ban
< user_unban POST /users/:username/unban(.:format) users#unban
< user_disable_invite POST /users/:username/disable_invitation(.:format) users#disable_invitation
< user_enable_invite POST /users/:username/enable_invitation(.:format) users#enable_invitation
old Story.filter_tags([1, 2, 3]):
Story.where.not( Tagging.select('TRUE').where('taggings.story_id = stories.id').where(tag_id: [1, 2, 3]).arel.exists)
SELECT `stories`.* FROM `stories` WHERE NOT (EXISTS (SELECT TRUE FROM `taggings` WHERE (taggings.story_id = stories.id) AND `taggings`.`tag_id` IN (1, 2, 3)))
new Story.filter_tags([1, 2, 3]):
Story.where(Story.arel_table[:id].not_in(Tagging.where(tag_id: [1, 2, 3]).select(:story_id).arel))
SELECT `stories`.* FROM `stories` WHERE `stories`.`id` NOT IN (SELECT `taggings`.`story_id` FROM `taggings` WHERE `taggings`.`tag_id` IN (1, 2, 3))
same story for Story.filter_tags_for(1):
Story.where(Story.arel_table[:id].not_in(Tagging.joins(tag: :tag_filters).where(tag_filters: { user_id: 1 }).select(:story_id).arel))
SELECT `stories`.* FROM `stories` WHERE `stories`.`id` NOT IN (SELECT `taggings`.`story_id` FROM `taggings` INNER JOIN `tags` ON `tags`.`id` = `taggings`.`tag_id` INNER JOIN `tag_filters` ON `tag_filters`.`tag_id` = `tags`.`id` WHERE `tag_filters`.`user_id` = 78)
So this is a clear improvement... but the EXPLAIN is exactly the same, MariaDB
recognized the opportunity:
+------+--------------+----------+-------+------------------------------------+-----------------+---------+------+-------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+----------+-------+------------------------------------+-----------------+---------+------+-------+--------------------------+
| 1 | PRIMARY | stories | ALL | NULL | NULL | NULL | NULL | 85056 | Using where |
| 2 | MATERIALIZED | taggings | index | story_id_tag_id,taggings_tag_id_fk | story_id_tag_id | 16 | NULL | 1 | Using where; Using index |
+------+--------------+----------+-------+------------------------------------+-----------------+---------+------+-------+--------------------------+
So this is a no-op on MariaDB, but I'm making the change because the
ActiveRecord is easier to read. Credit to Aaron Francis and Colleen Schnettler.
I deliberately didn't set a (user=nil) default for the arg; I'd rather get a
few 500s for any places that I missed updating than silently drop stories.
Matches impact of the comment/story by using the vote count as a penalty.
Flags effectively count extra because they -1 when applied and -2 when the mod
removes.