# 在插件中添加一个自定义的每用户设置

**URL:** https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048
**Category:** Developers
**Tags:** plugin-guides, how-to
**Created:** [2018年八月6日 20:39 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048 "2018-08-06T20:39:54Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![gdpelican](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gdpelican/32/81308_2.png) [@gdpelican](https://meta.discourse.org/u/gdpelican)
#### Post date: [2018年八月6日 20:39 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/1 "2018-08-06T20:39:54Z")

</div>

我刚刚经历了这个过程，经历了很多试错，所以我想记录下我的发现，以帮助接下来的开发者。

我需要的内容：

- 注册您的自定义字段类型（我的是布尔类型，默认是字符串类型）

- 注册该自定义字段应由用户可编辑。语法与 [`params.permit(...)`](https://edgeapi.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit) 匹配

- 将它们添加到 CurrentUserSerializer 序列化的字段中

- 创建一个组件来显示您的用户偏好设置

- 将该组件连接到偏好设置插件的某个出口（我的位于用户偏好设置下的 ‘interface’ 中）

- 确保在该偏好设置选项卡上保存“自定义字段”

* * *

本文档已进行版本控制 - 请在 [GitHub](https://github.com/discourse/discourse-developer-docs/blob/main/docs/04-plugins/08-user-settings.md) 上提出更改建议。

---

<div class="post-metadata">

### Author: ![LeoMcA](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/leomca/32/87233_2.png) [@LeoMcA](https://meta.discourse.org/u/LeoMcA)
#### Post date: [2018年八月7日 12:21 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/2 "2018-08-07T12:21:49Z")

</div>

Nice! I attempted the same in [GitHub - mozilla/discourse-post-read-email: INACTIVE - http://mzl.la/ghe-archive - A discourse plugin to give users the option of marking posts as read when emailed · GitHub](https://github.com/mozilla/discourse-post-read-email) and arrived at almost the same result.

My only differences were I didn’t hunt down `User.register_custom_field_type` and so used my own [ugly workaround](https://github.com/mozilla/discourse-post-read-email/blob/master/assets/javascripts/discourse/connectors/user-preferences-emails-pref-email-settings/post-read-email.js.es6#L6-L11). (I’ll switch to `register_custom_field_type` when I get the chance.)

And I think I came up with a slightly neater solution for saving the field, I [patch the preferences controller to save custom fields alongside everything else](https://github.com/mozilla/discourse-post-read-email/blob/master/assets/javascripts/discourse/initializers/post-read-email.js.es6), so the field is saved when the “Save” button is clicked, rather than when it’s toggled:

```plaintext
import { withPluginApi } from 'discourse/lib/plugin-api'

export default {
  name: 'post-read-email',
  initialize () {
     withPluginApi('0.8.22', api => {

       api.modifyClass('controller:preferences/emails', {
         actions: {
           save () {
             this.saveAttrNames.push('custom_fields')
             this._super()
           }
         }
       })

     })
  }
}

```

This should work for all preferences controllers, as they all seem to use `saveAttrNames`.

---

<div class="post-metadata">

### Author: ![gdpelican](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gdpelican/32/81308_2.png) [@gdpelican](https://meta.discourse.org/u/gdpelican)
#### Post date: [2018年八月9日 03:13 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/3 "2018-08-09T03:13:15Z")

</div>

As a follow-up here, it turns out that `inline-edit-checkbox` is available only in the adminjs package, meaning this is currently Bad Advice™. I’ve resorted to using the method suggested above alongside the `preference-checkbox` component

```plaintext
{{preference-checkbox labelKey="my_plugin.preferences.key" checked=model.custom_fields.my_field}}

```

which works for all users.

Also, @LeoMcA, I had to modify your preferences hack slightly to work with the interface page since `saveAttrNames` was a computed property there.

```plaintext
this.get('saveAttrNames').push('custom_fields')

```

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2018年九月4日 12:36 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/4 "2018-09-04T12:36:40Z")

</div>

Per [DEV: Allow plugins to whitelist specific user custom\_fields for editi… · discourse/discourse@4382fb5 · GitHub](https://github.com/discourse/discourse/commit/4382fb5facb035f5b414c6c7257dc828327a57c7), custom fields must now be added to a whitelist to allow editing by users. All that is needed is a single line in `plugin.rb`:

```ruby
register_editable_user_custom_field :my_field

```

@gdpelican @LeoMcA I have updated the OP with this extra step, and also pulled in the comments from your second and third posts. Please feel free to update anything else you feel is necessary.

---

<div class="post-metadata">

### Author: ![LeoMcA](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/leomca/32/87233_2.png) [@LeoMcA](https://meta.discourse.org/u/LeoMcA)
#### Post date: [2018年九月4日 12:50 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/5 "2018-09-04T12:50:19Z")

</div>

Perfect, thanks for this - I had fixing `discourse-post-read-email` on my todolist after last week’s security commit, and this makes it a whole lot easier!

Question (which may belong in a seperate post):

> [@gdpelican](#):
>
> register\_editable\_user\_custom\_field my\_preference: # For array type

Will this serialize the custom field as an array, even if it only has a single element in it? I’ve been having to use the following pattern in a seperate plugin, so that:

```ruby
user.custom_fields["field1"] = [:item]
user.save_custom_fields

```

becomes:

```ruby
Array(user.custom_fields["field1"])
> [:item]

```

rather than:

```ruby
user.custom_fields["field1"]
> :item

```

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2018年九月4日 13:04 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/6 "2018-09-04T13:04:18Z")

</div>

This change only deals with saving custom fields, so I don’t think it will affect how they are serialized.

That said, I do know that there are a lot of weird edge cases relating to custom fields which we are hoping to address in a few weeks time (after 2.1 is released). What you describe above looks like one of those weird cases that we need to improve.

---

<div class="post-metadata">

### Author: ![angus](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/angus/32/341715_2.png) [@angus](https://meta.discourse.org/u/angus)
#### Post date: [2018年九月4日 23:51 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/7 "2018-09-04T23:51:29Z")

</div>

Note that if the user custom field is a JSON string, you need to include the keys, or an empty hash (if the keys are dynamic), for it to be permitted, e.g.

```plaintext
register_editable_user_custom_field geo_location: {}

```

---

<div class="post-metadata">

### Author: ![angus](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/angus/32/341715_2.png) [@angus](https://meta.discourse.org/u/angus)
#### Post date: [2018年九月6日 08:48 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/8 "2018-09-06T08:48:18Z")

</div>

hm, in a slight pickle actually. If the custom field is JSON, in order to save it, you need to pass a hash, i.e.

```plaintext
register_editable_user_custom_field geo_location: {}

```

will permit

```plaintext
{"custom_fields"=>{"geo_location"=>{"lat"=>"-37.7989239", "lon"=>"144.8929753", "address"=>"Barkly Street, Footscray, City of Maribyrnong, Greater Melbourne, Victoria, 3011, Australia", "countrycode"=>"au", "city"=>"", "state"=>"Victoria", "country"=>"Australia", "postalcode"=>"3011", "boundingbox"=>["-37.7989854", "-37.7988961", "144.8928258", "144.8931743"], "type"=>"tertiary"}}>

```

However, if the param is empty (e.g. the user clears the field), the custom\_field is interpreted as a string

```plaintext
{"custom_fields"=>{"geo_location"=>"{}"}}

```

and is not permitted.

There isn’t an easy way around this in the current structure, i.e. the way `user_params` are added to in the `users_controller`

```plaintext
permitted << { custom_fields: User.editable_user_custom_fields }

```

Unless I’m missing something, perhaps some additional provision needs to be made for `user_custom_fields` that are typecast as JSON?

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2018年九月6日 08:54 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/9 "2018-09-06T08:54:09Z")

</div>

I had a similar problem with arrays, not sure if the same will work for JSON. To allow an empty array, you have to permit ‘scalar’ values as well as an array:

```ruby
register_editable_user_custom_field :geo_location
register_editable_user_custom_field geo_location: []

```

Or if you’re feeling fancy it can be combined into one line:

```ruby
register_editable_user_custom_field [:geo_location, geo_location: [] ]

```

This is the same behaviour as `params.permit(...)`, so I hesitate to call it a bug. Maybe we can call it a ‘quirk’ 😉

Let me know if that approach works for JSON - if not we can work out another solution

---

<div class="post-metadata">

### Author: ![angus](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/angus/32/341715_2.png) [@angus](https://meta.discourse.org/u/angus)
#### Post date: [2018年九月6日 08:58 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/10 "2018-09-06T08:58:20Z")

</div>

:facepalm: of course. Just add another. It’s been a long day. Thanks!

---

<div class="post-metadata">

### Author: ![gdpelican](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gdpelican/32/81308_2.png) [@gdpelican](https://meta.discourse.org/u/gdpelican)
#### Post date: [2018年九月18日 20:07 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/11 "2018-09-18T20:07:43Z")

</div>

Visiting this again, it feels like there are too many steps here for something that the core plugin system has a command for. On the backend, I have to write the following to make this go:

```plaintext
# plugin.rb
register_editable_user_custom_field :my_setting
User.register_custom_field_type 'my_setting', :boolean
DiscoursePluginRegistry.serialized_current_user_fields << 'my_setting'

```

but I feel like I should be able to do this:

```plaintext
# plugin.rb
register_editable_user_custom_field :my_setting, :boolean

```

You could even avoid people running into that nasty array snag by making the plugin system support the following cases:

```plaintext
register_editable_user_custom_field :my_setting, :array
register_editable_user_custom_field :my_setting, :object

```

@david

---

<div class="post-metadata">

### Author: ![JQ331](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@JQ331](https://meta.discourse.org/u/JQ331)
#### Post date: [2021年三月24日 11:37 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/12 "2021-03-24T11:37:44Z")

</div>

这篇帖子非常有用，清晰地展示了一个设置用户自定义字段的简单示例。是否有类似的内容可以指导如何设置一个简单的主题自定义字段？我正在尝试解决以下问题：

> [@How to add a super basic topic custom field](https://meta.discourse.org/t/how-to-add-a-super-basic-topic-custom-field/184229):
>
> I’m trying to understand how to add a custom field to topics, and working through a very basic example. Goal: Add a custom field called “sample\_field” to each topic created, with a simple string value. I’ve reviewed various examples, like [the poll plugin](https://github.com/discourse/discourse/tree/master/plugins/poll) and the [solved plugin](https://github.com/discourse/discourse/tree/master/plugins/poll) and [this discussion](https://meta.discourse.org/t/how-to-add-custom-field-to-topic/57948/2), but these plugins do so much more with their custom fields that I haven’t yet figured out the basic code you need. So I’m not quite there–my plugin.rb file is missing something (I think), and I haven’…

但尚未成功。非常希望能得到任何帮助。

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [2022年十二月29日 17:54 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/14 "2022-12-29T17:54:50Z")

</div>

David,

> [@gdpelican](#):
>
> `register_editable_user_custom_field my_preference: []`

这个似乎不再起作用了？

我们一直在使用：

```plaintext
  register_editable_user_custom_field [:geo_location, geo_location: {}] if defined? register_editable_user_custom_field
  register_editable_user_custom_field geo_location: {} if defined? register_editable_user_custom_field

```

来允许在用户自定义字段中保存 JSON 对象，但这现在阻止了站点的重建！

我们已经使用这个一段时间了。

我们在构建过程中收到的错误是：

```plaintext
ArgumentError: wrong number of arguments (given 0, expected 1)
/var/www/discourse/lib/plugin/instance.rb:170:in `register_editable_user_custom_field'
/var/www/discourse/plugins/discourse-locations/plugin.rb:95:in `block in activate!'

```

更令人困惑的是，这在 **开发环境中似乎有效** ，但在生产构建中却会失败。

如果删除它，站点可以构建，但用户自定义字段将无法保存，并且会静默失败。

我看不出这几年来有什么变化？：

> <https://github.com/discourse/discourse/blob/58479fe10b4b3683bfb6c6e16a517a742b311ee2/lib/plugin/instance.rb#L170>

是 Rails 的新版本现在阻止了这一点吗？

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2022年十二月29日 18:03 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/15 "2022-12-29T18:03:35Z")

</div>

@Falco 这可能与 Ruby 3.x 有关吗？

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [2022年十二月29日 18:04 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/16 "2022-12-29T18:04:15Z")

</div>

请注意，我本地安装了 2.7.1 用于开发（哎呀）……现在就修复。

---

<div class="post-metadata">

### Author: ![RGJ](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/rgj/32/523185_2.png) [@RGJ](https://meta.discourse.org/u/RGJ)
#### Post date: [2022年十二月29日 18:10 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/17 "2022-12-29T18:10:28Z")

</div>

是的，这绝对与 Ruby 3.1.x 相关。它在 2.7.x 上运行良好。

---

<div class="post-metadata">

### Author: ![Falco](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/falco/32/179432_2.png) [@Falco](https://meta.discourse.org/u/Falco)
#### Post date: [2022年十二月29日 18:11 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/18 "2022-12-29T18:11:57Z")

</div>

我的插件已安装并启用，在本地环境中使用当前的 Ruby 和默认设置，如何触发错误？

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [2022年十二月29日 18:13 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/19 "2022-12-29T18:13:24Z")

</div>

问题就在这里。`rbenv` 不允许我安装 `3.0.2` 之后的 Ruby 版本，而在开发环境中（我错过了什么？）我无法触发错误。但是，一旦你尝试在 `production_fixes` 分支上使用 Locations 插件构建一个当前的 `tests-passed` 实例（忽略名称，它已损坏）。

---

<div class="post-metadata">

### Author: ![Falco](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/falco/32/179432_2.png) [@Falco](https://meta.discourse.org/u/Falco)
#### Post date: [2022年十二月29日 18:14 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/20 "2022-12-29T18:14:55Z")

</div>

> [@merefield](#):
>
> `rbenv` 不允许我安装 `3.0.2` 之后的 Ruby

顺便说一下，是 `3.1.3`。如果你愿意听取建议，`asdf` 对我来说效果很好。

> [@merefield](#):
>
> 但只要你尝试在 `production_fixes` 分支上使用 Locations 插件构建一个当前的 `tests-passed` 实例（忽略这个名字，它坏了）。

好的，我会试试。

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [2022年十二月29日 18:34 UTC](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048/21 "2022-12-29T18:34:06Z")

</div>

抱歉，我想我实际上已经解决了那个分支的构建错误。我没想过它会起作用，但这似乎至少可以构建，现在只是在测试功能：

> <https://github.com/merefield/discourse-locations/pull/74/files>
>
> \* Fix to User Custom Field registration to allow User Custom Field to save whils…t also allowing site to build

似乎如果你使用这个技巧：

```plaintext
 register_editable_user_custom_field [:geo_location, geo_location: {}] if defined? register_editable_user_custom_field

```

正如 David 在上面建议的那样，它有效吗？

[下一頁](https://meta.discourse.org/t/add-a-custom-per-user-setting-in-a-plugin/94048.md?page=2)
