إضافة إعداد مخصص لكل مستخدم في إضافة

لقد مررتُ مؤخرًا بهذه العملية وشهدتُ الكثير من التجربة والخطأ، لذا قررتُ توثيق نتائجي لمساعدة المطور التالي الذي سيعبر هذا الطريق.

الأشياء التي احتجتُ إليها:

  • تسجيل نوع الحقل المخصص الخاص بك (كان لديّ من نوع boolean، بينما الافتراضي هو string)

    # plugin.rb
    User.register_custom_field_type 'my_preference', :boolean
    
  • تسجيل أن الحقل المخصص يجب أن يكون قابلاً للتعديل من قبل المستخدمين. يتطابق بناء الجملة مع params.permit(...)

    # plugin.rb
    register_editable_user_custom_field :my_preference # نوع قياسي (string, integer, إلخ.)
    register_editable_user_custom_field [:my_preference , my_preference : []] # لنوع المصفوفة
    register_editable_user_custom_field [:my_preference,  my_preference : {}] # لنوع json
    
  • إضافتها إلى الحقول المسلسلة مع CurrentUserSerializer

    # plugin.rb
    DiscoursePluginRegistry.serialized_current_user_fields << 'my_preference'
    
  • إنشاء مكون لعرض تفضيلات المستخدم الخاصة بك

    // assets/javascripts/discourse/templates/components/my-preference.hbs
    <label class="control-label">تفضيلاتي المخصصة!</label>
    {{preference-checkbox labelKey="my_plugin.preferences.key" checked=user.custom_fields.my_preference}}
    
  • ربط هذا المكون بأحد منافذ الإضافات الخاصة بالتفضيلات (كان لديّ تحت ‘interface’ في تفضيلات المستخدم)

    # assets/javascripts/discourse/connectors/user-preferences-interface/my-preference.hbs
    {{my-preference user=model}}
    
  • التأكد من حفظ ‘الحقول المخصصة’ في تبويب التفضيلات هذا

    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.get('saveAttrNames').push('custom_fields')
                 this._super()
               }
             }
           })
    
         })
      }
    }
    

هذا المستند يخضع لنظام التحكم بالإصدارات - يُرجى اقتراح التغييرات على GitHub.

33 إعجابًا

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 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. (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, so the field is saved when the “Save” button is clicked, rather than when it’s toggled:

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.

10 إعجابات

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

{{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.

this.get('saveAttrNames').push('custom_fields')
5 إعجابات

Per DEV: Allow plugins to whitelist specific user custom_fields for editi… · discourse/discourse@4382fb5 · GitHub, 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:

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.

8 إعجابات

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):

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:

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

becomes:

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

rather than:

user.custom_fields["field1"]
> :item
إعجابَين (2)

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.

6 إعجابات

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.

register_editable_user_custom_field geo_location: {}
3 إعجابات

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.

register_editable_user_custom_field geo_location: {}

will permit

{"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

{"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

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?

إعجاب واحد (1)

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:

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:

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’ :wink:

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

6 إعجابات

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

3 إعجابات

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:

# 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:

# 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:

register_editable_user_custom_field :my_setting, :array
register_editable_user_custom_field :my_setting, :object

@david

10 إعجابات

هذا منشور مفيد للغاية يوضح مثالًا مباشرًا لإعداد حقل مخصص للمستخدم. هل هناك شيء مشابه للمساعدة في إرشادك عبر حقل موضوع مخصص بسيط؟ أحاول العمل على ذلك هنا:

لكنني لم أتمكن من جعله يعمل بشكل صحيح بعد. سأقدّر حقًا أي مساعدة في هذا الشأن.

إعجاب واحد (1)

ديفيد،

هل يبدو أن هذا لم يعد يعمل؟

لقد كنا نستخدم:

  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 في حقل مخصص للمستخدم، ولكن هذا يمنع الآن إعادة بناء المواقع!

لقد كنا نستخدم هذا لبعض الوقت.

الخطأ الذي نحصل عليه أثناء البناء هو:

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!'

لإضافة إلى الارتباك، هذا يبدو أنه يعمل في بيئة التطوير، ولكنه يفشل فقط أثناء بناء الإنتاج.

إذا تمت إزالته، فسيتم بناء الموقع ولكن الحقل المخصص للمستخدم لن يتم حفظه وسيفشل بصمت.

لا أرى أن هذا قد تغير منذ سنوات؟:

هل هناك إصدار جديد من Rails يمنع هذا الآن؟

إعجابَين (2)

@Falco هل يمكن أن يكون هذا متعلقًا بـ Ruby 3.x؟

إعجابَين (2)

للعلم، لدي الإصدار 2.7.1 مثبتًا محليًا للتطوير (عفوًا)… أقوم بإصلاح ذلك الآن.

3 إعجابات

نعم، يتعلق الأمر بالتأكيد بـ Ruby 3.1.x. إنه يعمل بشكل جيد على 2.7.x.

إعجابَين (2)

لدي المكون الإضافي مثبتًا وممكّنًا بالإعدادات الافتراضية في بيئتي المحلية مع Ruby الحالي، كيف يمكنني تشغيل الخطأ؟

إعجاب واحد (1)

حسنًا، هذه هي المشكلة. rbenv لا يسمح لي بتثبيت Ruby بعد 3.0.2 وفي بيئة التطوير (؟ ماذا أفوت؟) لا يمكنني إثارة الخطأ في بيئة التطوير. ولكن بمجرد محاولة بناء نسخة حالية من tests-passed مع المكون الإضافي للمواقع (Locations Plugin) على الفرع production_fixes (تجاهل الاسم، إنه معطل).

إعجاب واحد (1)

إنها 3.1.3 بالمناسبة. إذا كنت منفتحًا على الاقتراحات، فإن asdf يعمل بشكل رائع بالنسبة لي.

رائع، سأجرب ذلك.

إعجابَين (2)

عذرًا، أعتقد أنني قتلت خطأ البناء بالفعل في هذا الفرع. لم أعتقد أن الأمر سينجح، لكن هذا يبدو أنه يبني على الأقل، وأنا أختبر الوظائف الآن فقط:

يبدو أنه إذا استخدمت هذه الحيلة:

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

كما اقترح ديفيد أعلاه، هل يعمل؟

إعجاب واحد (1)