Correct way to set yaml_column_permitted_classes for a custom class
Asked Answered
Y

5

7

Because of this security advisory serialized attributes need to use YAML.safe_load with a safe list of allowed Classes.

The problem I am having is that I want to use a custom class (Foo::Bar) and it seems like at least in Rails 6.1 that you need to set the allowed classes towards the beginning of the boot process in application.rb.

    config.active_record.yaml_column_permitted_classes = [
      Symbol,
      String,
      Foo::Bar
    ]

The problem is that running this (as is) inside application.rb gives this error:

 uninitialized constant AppName::Application::Foo (NameError)

If I add to the top of the application.rb a require statement, then the application boots fine, but, in dev I then get warnings (because classes and their constants are redefined)

 warning: previous definition of SomeConstant was here

The only cleanish way I've found is instead of using the config at all, you just set what rails really wants to be set which is this ->

# in some initializer
ActiveRecord::Base.yaml_column_permitted_classes = [
      Symbol,
      String,
      Foo::Bar
]

That doesn't give any errors, but, it feels like I'm going outside of what Rails wants me to do.

Is there a correct way to initialize custom classes in the application.rb ?

Yoko answered 5/10, 2022 at 9:5 Comment(0)
H
5

On Rails 7 what I have found is to put things into an initializer (I use config/initializers/active_record.rb and use the Rails.application.config.after_initialize hook.

You end up with something like this:

# config/initializers/active_record.rb
# this has to be here because we need MyClass, which isn't loaded early enough to do in environments/production.rb

Rails.application.config.after_initialize do
  ActiveRecord.yaml_column_permitted_classes += [
    Date,
    BigDecimal,
    ActiveSupport::TimeWithZone,
    Time,
    ActiveSupport::TimeZone,
    ActiveSupport::HashWithIndifferentAccess,
    MyClass
  ]
end

To be honest, you can also do this in config/application.rb:

    config.after_initialize do
      ActiveRecord.yaml_column_permitted_classes += [
        Date,
        BigDecimal,
        ActiveSupport::TimeWithZone,
        Time,
        ActiveSupport::TimeZone,
        ActiveSupport::HashWithIndifferentAccess,
        MyClass
      ]
    end
Hilltop answered 4/1, 2023 at 6:22 Comment(4)
NOTE: config gets passed into the after_initialize block so I just had to use config.active_record.yaml_column_permitted_classes = [ ... ] inside of the config.after_initialize and I kept it in config/application.rb as well to keep it clean and consistent.Wentzel
I’d suggest doing += instead of = so that you don’t have to include Symbol and so that you don’t overwrite previous definitions that future Rails versions or gems may add.Broomrape
Fair point @Sunny. Code edited.Hilltop
I had this same issue on 6.1.7.6 and found that the classnames can be strings, so no loader precedence issues. Just leave the config as-is and put quotes around your custom classes. 🎉Slug
T
1

The correct way to set yaml_column_permitted_classes for a custom class is to use the config.active_record.yaml_column_permitted_classes setting in application.rb. The issue you are running into is that the application.rb is loaded before the Foo::Bar class is defined. You can fix this by moving the config.active_record.yaml_column_permitted_classes setting to an initializer that is loaded after the Foo::Bar class is defined. For example, you could create an initializer in config/initializers that looks like this:

# config/initializers/active_record.rb
config.active_record.yaml_column_permitted_classes = [
  Symbol,
  String,
  Foo::Bar
]

This will ensure that the Foo::Bar class is defined before the config.active_record.yaml_column_permitted_classes setting is evaluated.

Tain answered 5/10, 2022 at 10:29 Comment(1)
Have you tested this? It doesn't actually work. With my way, when I start the console and run ActiveRecord::Base.yaml_column_permitted_classes => [Symbol, String, Foo::Bar] if I move it to an initializer it is returned as [Symbol] only. That's the entire problem (I tried it in an initializer)Yoko
B
1

An all-rounded approach that worked for me in Rails 7.0 was this initializer:

# config/initializers/active_record.rb

Rails.application.config.after_initialize do |app|
  ActiveRecord.yaml_column_permitted_classes =
    app.config.active_record.yaml_column_permitted_classes += [
      YourClass,
    ]
end

It’s a good practice to use += in order not to overwrite the defaults that Rails or other gems might add.

Broomrape answered 31/1, 2023 at 13:53 Comment(0)
B
0

Moving this setting into an initializer worked for me. The initializer runs after my custom classes have been loaded, so I no longer get the uninitialized constant error. When I start the console and run ActiveRecord::Base.yaml_column_permitted_classes I see the full list I set in the initializer.

# config/initializers/active_record.rb
ActiveRecord::Base.yaml_column_permitted_classes = [
  Symbol,
  String,
  Foo::Bar
]
Battat answered 31/10, 2022 at 21:29 Comment(0)
D
0

Just specify as strings. Works at least on 5.2

    config.active_record.yaml_column_permitted_classes = %w[Symbol Time Date BigDecimal OpenStruct CustomClass]
Dwell answered 2/5, 2023 at 19:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.