Commit Graph

3098 Commits

Author SHA1 Message Date
709fa24558 DEV: move sidebar community section to database (#21166)
* DEV: move sidebar community section to database

Before, community section was hard-coded. In the future, we are planning to allow admins to edit it. Therefore, it has to be moved to database to `custom_sections` table.

Few steps and simplifications has to be made:
- custom section was hidden behind `enable_custom_sidebar_sections` feature flag. It has to be deleted so all forums, see community section;
- migration to add `section_type` column to sidebar section to show it is a special type;
- migration to add `segment` column to sidebar links to determine if link should be displayed in primary section or in more section;
- simplify more section to have one level only (secondary section links are merged);
- ensure that links like `everything` are correctly tracking state;
- make user an anonymous links position consistence. For example, from now on `faq` link for user and anonymous is visible in more tab;
- delete old community-section template.
2023-05-04 12:14:09 +10:00
afc1611be7 DEV: Disable SearchIndexer after fabrication (#21378)
SearchIndexer is only automatically disabled in `before_all` and `before` blocks which means at the start
of test runs. Enabling the SearchIndexer in one `fab!` block will affect
all other `fab!` blocks which is not ideal as we may be indexing stuff
for search when we don't need to.
2023-05-04 09:20:52 +08:00
783c935dcb DEV: Set limits for text fields in reviewables 2023-05-03 09:54:54 +02:00
01dc461cc2 FIX: Disallow invisible Unicode characters in usernames (#21331)
The list of excluded characters is based on https://invisible-characters.com/ and the list of invisible characters used by Visual Studio Code (https://github.com/hediet/vscode-unicode-data)
2023-05-02 17:34:53 +10:00
e2fbf4865a DEV: Check if video thumbnails site setting is enabled (#21306) 2023-04-28 14:08:20 -06:00
c4c2da83b4 DEV: Set limits for text fields in badges 2023-04-27 17:22:32 +02:00
a89b3c27aa DEV: set limits for text fields on groups 2023-04-27 13:58:46 +02:00
Sam
2ccc5fc66e FEATURE: add support for figure and figcaption tags in embeddings (#21276)
Many blog posts use these to illustrate and images were previously omitted

Additionally strip superfluous HTML and BODY tags from embed HTML.

This was incorrectly returned from server.
2023-04-27 19:57:06 +10:00
37cc056c1b FIX: Ensure group-filtered group user event webhooks fire (#21254)
Group user event webhooks filtered by group fail silently
because the `group_ids` job arg wasn't being passed into the job.

This change add's `group_ids` to the `EmitWebHookEvent` jobs queued for
`user_added_to_group` and `user_removed_from_group` events.
2023-04-26 22:38:28 +00:00
cdf1589a85 FEATURE: Add support for user badge revocation webhook events (#21204)
Currently, only user badge grants emit webhook events. This change
extends the `user_badge` webhook to emit user badge revocation events.

A new `user_badge_revoked` event has been introduced instead of relying
on the existing `user_badge_removed` event. `user_badge_removed` emitted
just the `badge_id` and `user_id` which aren't helpful for generating a
meaningful webhook payload for revoked(deleted) user badges.

The new event emits  the user badge object.
2023-04-24 20:36:40 +00:00
6ae0c42c01 FIX: Do not overwrite existing thumbnails (#21199)
* FIX: Do not overwrite existing thumbnails

When auto generating video thumbnails they should not overwrite any
existing topic thumbnails.

This also addresses an issue with capitalized file extensions like .MOV
that were being excluded.

* Update app/models/post.rb

Remove comment

Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>

---------

Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
2023-04-21 13:33:33 -06:00
76874b7098 FIX: 500 error when adding restricted category tags (#21147)
This fixes a 500 error that occurs when adding a tag to a category's
restricted tag list if the category's restricted tags already included a
synonym tag.
2023-04-18 11:01:11 -06:00
f3f30d6865 SECURITY: Encode embed url (#21133)
The embed_url in "This is a companion discussion..." could be used for
XSS.

Co-authored-by: Blake Erickson <o.blakeerickson@gmail.com>
2023-04-18 15:05:29 +08:00
a299c61d72 DEV: Remove hardcoded user_id in spec (#21111)
Followup to 08ff6eebad83d921ad0d175aa02332fb04333ff7,
we can just test this using the original problem in
https://meta.discourse.org/t/-/172572, which is that
SiteSetting.default_categories_normal had duplicate
IDs.
2023-04-17 16:35:22 +10:00
967010e545 FEATURE: Add an emoji deny list site setting (#20929)
This feature will allow sites to define which emoji are not allowed. Emoji in this list should be excluded from the set we show in the core emoji picker used in the composer for posts when emoji are enabled. And they should not be allowed to be chosen to be added to messages or as reactions in chat.

This feature prevents denied emoji from appearing in the following scenarios:
- topic title and page title
- private messages (topic title and body)
- inserting emojis into a chat
- reacting to chat messages
- using the emoji picker (composer, user status etc)
- using search within emoji picker

It also takes into account the various ways that emojis can be accessed, such as:
- emoji autocomplete suggestions
- emoji favourites (auto populates when adding to emoji deny list for example)
- emoji inline translations
- emoji skintones (ie. for certain hand gestures)
2023-04-13 15:38:54 +08:00
9238767f7e FEATURE: Persist password hashing algorithm/params in database (#20980)
Previously, Discourse's password hashing was hard-coded to a specific algorithm and parameters. Any changes to the algorithm or parameters would essentially invalidate all existing user passwords.

This commit introduces a new `password_algorithm` column on the `users` table. This persists the algorithm/parameters which were use to generate the hash for a given user. All existing rows in the users table are assumed to be using Discourse's current algorithm/parameters. With this data stored per-user in the database, we'll be able to keep existing passwords working while adjusting the algorithm/parameters for newly hashed passwords.

Passwords which were hashed with an old algorithm will be automatically re-hashed with the new algorithm when the user next logs in.

Values in the `password_algorithm` column are based on the PHC string format (https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md). Discourse's existing algorithm is described by the string `$pbkdf2-sha256$i=64000,l=32$`

To introduce a new algorithm and start using it, make sure it's implemented in the `PasswordHasher` library, then update `User::TARGET_PASSWORD_ALGORITHM`.
2023-04-11 10:16:28 +01:00
63a0466548 FIX: improve performance of UserStat.ensure_consistency (#21044)
Optimize `UserStatpost_read_count` calculation.

In addition, tests were updated to fail when code is not evaluated. Creation of PostTiming was updating `post_read_count`. Count it has to be reset to ensure that ensure_consitency correctly calculates result.

Extracting users seen in the last hour to separate Common Table Expression reduces the amount of processed rows.

Before
```
Update on user_stats  (cost=267492.07..270822.95 rows=2900 width=174) (actual time=12606.121..12606.127 rows=0 loops=1)
  ->  Hash Join  (cost=267492.07..270822.95 rows=2900 width=174) (actual time=12561.814..12603.689 rows=10 loops=1)
        Hash Cond: (user_stats.user_id = x.user_id)
        Join Filter: (x.c <> user_stats.posts_read_count)
        Rows Removed by Join Filter: 67
        ->  Seq Scan on user_stats  (cost=0.00..3125.34 rows=75534 width=134) (actual time=0.014..39.173 rows=75534 loops=1)
        ->  Hash  (cost=267455.80..267455.80 rows=2901 width=48) (actual time=12558.613..12558.617 rows=77 loops=1)
              Buckets: 4096  Batches: 1  Memory Usage: 39kB
              ->  Subquery Scan on x  (cost=267376.03..267455.80 rows=2901 width=48) (actual time=12168.601..12558.572 rows=77 loops=1)
                    ->  GroupAggregate  (cost=267376.03..267426.79 rows=2901 width=12) (actual time=12168.595..12558.525 rows=77 loops=1)
                          Group Key: pt.user_id
                          ->  Sort  (cost=267376.03..267383.28 rows=2901 width=4) (actual time=12100.490..12352.106 rows=2072830 loops=1)
                                Sort Key: pt.user_id
                                Sort Method: external merge  Disk: 28488kB
                                ->  Nested Loop  (cost=1.28..267209.18 rows=2901 width=4) (actual time=0.040..11528.680 rows=2072830 loops=1)
                                      ->  Nested Loop  (cost=0.86..261390.02 rows=13159 width=8) (actual time=0.030..3492.887 rows=3581648 loops=1)
                                            ->  Index Scan using index_users_on_last_seen_at on users u  (cost=0.42..89.71 rows=28 width=4) (actual time=0.010..0.201 rows=78 loops=1)
                                                  Index Cond: (last_seen_at > '2023-04-11 00:22:49.555537'::timestamp without time zone)
                                            ->  Index Scan using index_post_timings_on_user_id on post_timings pt  (cost=0.44..9287.60 rows=4455 width=8) (actual time=0.081..38.542 rows=45919 loops=78)
                                                  Index Cond: (user_id = u.id)
                                      ->  Index Scan using forum_threads_pkey on topics t  (cost=0.42..0.44 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=3581648)
                                            Index Cond: (id = pt.topic_id)
                                            Filter: ((deleted_at IS NULL) AND ((archetype)::text = 'regular'::text))
                                            Rows Removed by Filter: 0
Planning Time: 0.692 ms
Execution Time: 12612.587 ms
```
After
```
Update on user_stats  (cost=9473.60..12804.30 rows=2828 width=174) (actual time=677.724..677.729 rows=0 loops=1)
  ->  Hash Join  (cost=9473.60..12804.30 rows=2828 width=174) (actual time=672.536..677.706 rows=1 loops=1)
        Hash Cond: (user_stats.user_id = x.user_id)
        Join Filter: (x.c <> user_stats.posts_read_count)
        Rows Removed by Join Filter: 54
        ->  Seq Scan on user_stats  (cost=0.00..3125.34 rows=75534 width=134) (actual time=0.012..23.977 rows=75534 loops=1)
        ->  Hash  (cost=9438.24..9438.24 rows=2829 width=48) (actual time=647.818..647.822 rows=55 loops=1)
              Buckets: 4096  Batches: 1  Memory Usage: 37kB
              ->  Subquery Scan on x  (cost=9381.66..9438.24 rows=2829 width=48) (actual time=647.409..647.805 rows=55 loops=1)
                    ->  HashAggregate  (cost=9381.66..9409.95 rows=2829 width=12) (actual time=647.403..647.786 rows=55 loops=1)
                          Group Key: pt.user_id
                          Batches: 1  Memory Usage: 121kB
                          ->  Nested Loop  (cost=1.86..9367.51 rows=2829 width=4) (actual time=0.056..625.245 rows=120022 loops=1)
                                ->  Nested Loop  (cost=1.44..3692.96 rows=12832 width=8) (actual time=0.047..171.754 rows=217440 loops=1)
                                      ->  Nested Loop  (cost=1.00..254.63 rows=25 width=12) (actual time=0.030..1.407 rows=56 loops=1)
                                            Join Filter: (u.id = user_stats_1.user_id)
                                            ->  Nested Loop  (cost=0.71..243.08 rows=25 width=8) (actual time=0.018..1.207 rows=87 loops=1)
                                                  ->  Index Scan using index_users_on_last_seen_at on users u  (cost=0.42..86.71 rows=27 width=4) (actual time=0.009..0.156 rows=87 loops=1)
                                                        Index Cond: (last_seen_at > '2023-04-11 00:47:07.437568'::timestamp without time zone)
                                                  ->  Index Only Scan using user_stats_pkey on user_stats us  (cost=0.29..5.79 rows=1 width=4) (actual time=0.011..0.011 rows=1 loops=87)
                                                        Index Cond: (user_id = u.id)
                                                        Heap Fetches: 87
                                            ->  Index Scan using user_stats_pkey on user_stats user_stats_1  (cost=0.29..0.45 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=87)
                                                  Index Cond: (user_id = us.user_id)
                                                  Filter: (posts_read_count < 10000)
                                                  Rows Removed by Filter: 0
                                      ->  Index Scan using index_post_timings_on_user_id on post_timings pt  (cost=0.44..92.98 rows=4455 width=8) (actual time=0.036..2.492 rows=3883 loops=56)
                                            Index Cond: (user_id = user_stats_1.user_id)
                                ->  Index Scan using forum_threads_pkey on topics t  (cost=0.42..0.44 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=217440)
                                      Index Cond: (id = pt.topic_id)
                                      Filter: ((deleted_at IS NULL) AND ((archetype)::text = 'regular'::text))
                                      Rows Removed by Filter: 0
Planning Time: 1.406 ms
Execution Time: 677.817 ms
```
2023-04-11 12:28:08 +10:00
b72282123b FIX: public sidebar sections belong to system user (#20972)
Before, public sidebar sections were belonging to admin. However, a better choice is system user.
2023-04-05 10:52:18 +10:00
fd34032db2 DEV: Support filter for topics in specific subcategories on /filter (#20927)
This commit adds support for filtering for topics in specific
subcategories via the categories filter query language.

For example: `category:documentation:admins` will filter for topics and
subcategory topics in
the category with slug "admins" whose parent category has the slug
"documentation".

The `=` prefix can also be used such that
`=category:documentation:admins` will exclude subcategory topics of the
category with slug "admins" whose parent category has the slug
"documentation".
2023-04-03 18:36:59 +08:00
c86d772277 FIX: Drop internal URL validation for paths in sidebar (#20891)
`Rails.application.routes.recognize_path(value)` was not working for /admin paths because StaffConstraint.new requires user to check permission.

This validation is not bringing much value, and the easiest way is to drop it. In the worse case scenario, a user will have an incorrect link in their sidebar.

Bug reported: https://meta.discourse.org/t/custom-sidebar-sections-being-tested-on-meta/255303/66
2023-03-31 13:26:56 +08:00
9518e47204 FEATURE: ability to bulk_remove users from a group (#20876) 2023-03-30 08:06:36 -05:00
aa09a78d14 DEV: Remove diffhtml_preview for video thumbnails (#20865)
Video thumbnail generation is no longer dependent on
`enable_diffhtml_preview` being enabled.
2023-03-28 12:38:50 -06:00
08ff6eebad FIX: skip category preference update if already set by group. (#20823)
`default_categories_*` site settings will update the category preferences on user creation. But it shouldn't update the user's category preference if a group's setting already updated it for that user.
2023-03-28 19:43:01 +05:30
ebada4a6b0 DEV: More specific API to including extra associations in CategoryList (#20790) 2023-03-23 12:39:38 -05:00
32aa821f12 DEV: Add preload API to CategoryList (#20778) 2023-03-22 15:12:08 -05:00
7b01576c8d DEV: Remove emoji cache dead code (#20764)
The cache is already not shared between app servers that have different
app_versions, so this check was redundant.
2023-03-21 12:33:12 -05:00
a0a6f6d71b FIX: Fix the emoji toned regexes (#20763) 2023-03-21 11:48:55 -05:00
da0d20d4a9 DEV: Refactor svg sprite parsing (#20727)
There was a lot of duplication in the svg parsing and coercion code. This reduces that duplication and causes svg sprite parsing to happen earlier so that more computation is cached.
2023-03-20 11:41:23 -05:00
84f590ab83 DEV: Store theme sprites in the DB (#20501)
Let's avoid fetching sprites from the CDN during page rendering.
2023-03-14 13:11:45 -05:00
bb317bd554 DEV: Update the rubocop setup (#20668) 2023-03-14 11:42:11 +01:00
943068a634 FIX: Welcome topic banner showing after general category is deleted (#20639)
If you happen to delete the general category before editing the welcome
topic, the banner will still display. This fix adds a after destroy hook
that will clear the entries for the welcome topic banner in the redis
cache.
2023-03-10 12:33:12 -07:00
87ec058b8b FEATURE: Configurable auto-bump cooldown (#20507)
Currently the auto-bump cooldown is hard-coded to 24 hours.

This change makes the highlighted 24 hours part configurable (defaulting to 24 hours), and the rest of the process remains the same.

This uses the new CategorySetting model associated with Category. We decided to add this because we want to move away from custom fields due to the lack of type casting and validations, but we want to keep the loading of these optional as they are not needed for almost all of the flows.

Category settings will be back-filled to all categories as part of this change, and creating a new category will now also create a category setting.
2023-03-10 13:45:01 +08:00
f144c64e13 Generate thumbnail images for video uploads (#19801)
* FEATURE: Generate thumbnail images for uploaded videos

Topics in Discourse have a topic thumbnail feature which allows themes
to show a preview image before viewing the actual Topic.

This PR allows for the ability to generate a thumbnail image from an
uploaded video that can be use for the topic preview.
2023-03-09 09:26:47 -07:00
22bccef8f4 FIX: set external flag before validation (#20599)
Previously, `before_save` callback was used but `before_validation` has to be used to set external flag.
2023-03-09 10:44:54 +11:00
27f7cf18b1 FIX: Don’t email suspended users from group PM
Currently, when a suspended user belongs to a group PM (private message
with more than two people in it) and a staff member sends a message to
this group PM, then the suspended user will receive an email.
This happens because a suspended user can only receive emails from staff
members. But in this case, this can be seen as a bug as the expected
behavior would be instead to not send any email to the suspended user. A
staff member can participate in active discussions like any other
member and so their messages in this context shouldn’t be treated
differently than the ones from regular users.

This patch addresses this issue by checking if a suspended user receives
a message from a group PM or not. If that’s the case then an email won’t
be sent no matter if the post originated from a staff member or not.
2023-03-08 15:53:53 +01:00
12436d054d DEV: Remove badge_granted_title column from user_profiles (#20476)
That column is obsolete since we added the `granted_title_badge_id` column in 2019 (56d3e29a698e704f647cf2e500d0d94d7d9e21bf). Having both columns can lead to inconsistencies (mostly due to old data from before 2019).

For example, `BadgeGranter.revoke_ungranted_titles!` doesn't work correctly if `badge_granted_title` is `false` while `granted_title_badge_id` points to the badge that is used as title.
2023-03-08 13:37:20 +01:00
360d0dde65 DEV: Change Bookmarkable registration to DiscoursePluginRegistry (#20556)
Similar spirit to e195e6f614de7a4c4f151ad947578fb69f8917f0,
this moves the Bookmarkable registration to DiscoursePluginRegistry
so plugins which are not enabled do not register additional
bookmarkable classes.
2023-03-08 10:39:12 +10:00
e52bbc1230 UX/DEV: Review queue redesign fixes (#20239)
* UX: add type tag and design update

* UX: clarify status copy in reviewQ

* DEV: switch to selectKit

* UX: color approve/reject buttons in RQ

* DEV: regroup actions

* UX: add type tag and design update

* UX: clarify status copy in reviewQ

* Join questions for flagged post with "or" with new I18n function
* Move ReviewableScores component out of context
* Add CSS classes to reviewable-item based on human type

* UX: add table header for scoring

* UX: don't display % score

* UX: prefix modifier class with dash

* UX: reviewQ flag table styling

* UX: consistent use of ignore icon

* DEV: only show context question on pending status

* UX: only show table headers on pending status

* DEV: reviewQ regroup actions for hidden posts

* UX: reviewQ > approve/reject buttons

* UX: reviewQ add fadeout

* UX: reviewQ styling

* DEV: move scores back into component

* UX: reviewQ mobile styling

* UX: score table on mobile

* UX: reviewQ > move meta info outside table

* UX: reviewQ > score layout fixes

* DEV: readd `agree_and_keep` and fix the spec tests.

* Fix the spec tests

* fix the quint test

* DEV: readd deleting replies

* UX: reviewQ copy tweaks

* DEV: readd test for ignore + delete replies

* Remove old

* FIX: Add perform_ignore back in for backwards compat

* DEV: add an action alias `ignore` for `ignore_and_do_nothing`.

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
Co-authored-by: Vinoth Kannan <svkn.87@gmail.com>
2023-03-02 16:40:53 +01:00
96d03ea9c0 FIX: No small action created when a non-author removes itself from a PM (#20502)
Fixes a small issue where allowed user removes themselves from a private message before the post activity (small action) is created.

I also added some test coverage to prevent regression.

/t/92811
2023-03-02 13:47:54 +08:00
e195e6f614 DEV: Move about_stat_groups to DiscoursePluginRegistry (#20496)
Follow up to 098ab29d4150a5a39880d59ef6cad5f6795a18fd. Since
we just used a `cattr_reader` on `About` this was not safe
for multisite, since some sites could have the chat plugin
enabled and some may not. Using `DiscoursePluginRegistry` gets
around this issue, and makes it so the chat stats only show
for a site if `chat_enabled` is true.
2023-03-02 08:10:16 +10:00
666b4a7e6b DEV: Define form template field inputs (#20430) 2023-03-01 11:07:13 -08:00
Sam
71be74ffd3 FIX: recalculating trust levels was not working (#20492)
The recalculate code was never firing cause TrustLevel.calculate unconditionally
returned a trust level. (albeit a wrong one)

New code ensures we only bypass promotion checks for cases where trust level
is locked.

see: https://meta.discourse.org/t/user-trust-level-resets-to-zero-when-unlocked/255444
2023-03-01 15:35:21 +11:00
8a2995f719 FIX: only show approved users in search_user results when site setting enabled (#20493)
Returns only approved users when using @ in composer (if must_approve_users site setting enabled).
2023-03-01 12:23:29 +08:00
44b7706a2b UX: Skip applying link-type watched words to user custom fields (#20465)
We currently apply type: :link watched words to custom user fields. This makes the user card pretty ugly because we don't allow html / links there. Additionally, the admin UI also does not say that we apply this to custom user fields, but only words in posts.

So this PR is to remove the replacement of link-type watch words for custom user fields.
2023-03-01 10:43:34 +08:00
d92fd30d23 FIX: do not escape slash for category text description (#20460)
Original solution to use `description` instead of `text_description` was wrong: https://github.com/discourse/discourse/pull/20436

Problem is that we have to escape HTML tags.

However, we would like to use escape method which is keep `/` intact.   Expected behavior is given by  ERB::Util.html_escape instead of Rack::Utils.escape_html

/t/92015
2023-02-27 12:48:48 +11:00
3cadeaf90f FIX: Add support for Europe/Kyiv timezone (#20394) 2023-02-21 11:52:04 -05:00
7ef482a292 REFACTOR: Fix pluralized strings in chat plugin (#20357)
* FIX: Use pluralized string

* REFACTOR: Fix misuse of pluralized string

* REFACTOR: Fix misuse of pluralized string

* DEV: Remove linting of `one` key in MessageFormat string, it doesn't work

* REFACTOR: Fix misuse of pluralized string

This also ensures that the URL works on subfolder and shows the site setting link only for admins instead of staff. The string is quite complicated, so the best option was to switch to MessageFormat.

* REFACTOR: Fix misuse of pluralized string

* FIX: Use pluralized string

This also ensures that the URL works on subfolder and shows the site setting link only for admins instead of staff.

* REFACTOR: Correctly pluralize reaction tooltips in chat

This also ensures that maximum 5 usernames are shown and fixes the number of "others" which was off by 1 if the current user reacted on a message.

* REFACTOR: Use translatable string as comma separator

* DEV: Add comment to translation to clarify the meaning of `%{identifier}`

* REFACTOR: Use translatable comma separator and use explicit interpolation keys

* REFACTOR: Don't interpolate lowercase channel status

* REFACTOR: Fix misuse of pluralized string

* REFACTOR: Don't interpolate channel status

* REFACTOR: Use %{count} interpolation key

* REFACTOR: Fix misuse of pluralized string

* REFACTOR: Correctly pluralize DM chat channel titles
2023-02-20 10:31:02 +01:00
6fa9a8990d Add username template variables to Welcome User email template (#20352)
* Add username and name_or_username variables to SystemMessage defaults

* Allow username and name variables on welcome_user email template overrides

* Satisfy linting

* Add test
2023-02-17 18:38:21 -03:00
Sam
f96ef33856 FIX: dominant color not working for 16bit images (#20300)
16 bit images were not returning the correct dominant color due truncation

The routine expected an 8bit color eg: #FFAA00, but ended up getting a 16bit one eg: #FFFAAA000. This caused a truncation, which leads to wildly off colors.
2023-02-15 12:41:04 +11:00
25a226279a DEV: Replace #pluck_first freedom patch with AR #pick in core (#19893)
The #pluck_first freedom patch, first introduced by @danielwaterworth has served us well, and is used widely throughout both core and plugins. It seems to have been a common enough use case that Rails 6 introduced it's own method #pick with the exact same implementation. This allows us to retire the freedom patch and switch over to the built-in ActiveRecord method.

There is no replacement for #pluck_first!, but a quick search shows we are using this in a very limited capacity, and in some cases incorrectly (by assuming a nil return rather than an exception), which can quite easily be replaced with #pick plus some extra handling.
2023-02-13 12:39:45 +08:00