Dynamic record attributes with Ruby on Rails and Mobility

67 Views Asked by At

With Ruby on Rails and Mobility (localization/translation/i18n), how to let different records of a same model have different translated attributes without splitting into separate model classes?

Let's say a Page model has a type attribute which can be welcome or about. When type has the value welcome, then the record should have translatable attributes hero_text, etc. When type has the value about, then the record should have translatable attributes section_1, etc. Page records may also share some common translatable attributes such as title.

class Page < ApplicationRecord
  enum type: %i(welcome about)
  translates :title, backend: :key_value

  # Imaginary: this doesn't work
  translates *->{ translatable_attributes }, backend: :key_value

  def translatable_attributes
    case type
    when :welcome
      %i(hero_text ...)
    when :about
      %i(section_1 ...)
    end
  end

  # Rather than inferring `translatable_attributes` from `type` as above,
  # it may also be a JSON array stored in the DB record, for full flexibility
end

Extra context

Using a key-value back-end already ensures the DB schema remains the same regardless of which attributes are translated. We want to take advantage of this Mobility design feature (and Action Text). (As far as non-translatable attributes are concerned, attr_json also offers a similar benefit.)

In our example, marketing pages may each have a unique design, and therefore unique translated attributes, but they are still all "pages" and we still want to manage them as a collection of pages (one Page model class for many page records) rather than needing to split the modelling for each and every concrete record (one WelcomePage model class for the welcome page with a single record, one AboutPage model class for the about page with a single record, etc).

Inheritance (e.g single-table inheritance STI) or delegated types feels a somewhat incorrect solution: a new model class needs to be defined as soon as a new marketing page with a unique design is needed. Also, in our tests with STI, we found that def self.model_name; ActiveModel::Name.new(base_class); end) needed to be defined in each subclass to avoid having different routes/controllers for each subtype, which feels hacky; and defining shared translated attributes in the Page superclass (translates :title) does not seem to have any effect for some reason, forcing us to repeat that code in each subclass. We also failed to use delegated types as they seem to require a separate DB table for each concrete subtype, which defeats the purpose outlined here (one new DB table would be needed every time we create a new page design).

We would love to enable non-technical team members to configure translatable attributes they need directly through a UI instead of through code, i.e. to approach the experience of a CMS.

0

There are 0 best solutions below