Mobility Ruby Gem

repository·master·Indexed 22 days ago

https://github.com/shioyama/mobility

A Ruby gem for storing and retrieving translations as attributes on a class. It supports multiple ORMs, including ActiveRecord (>= 7.0) and Sequel (>= 4.0), and provides various storage backends such as KeyValue, table, column, JSON, JSONB, and hstore. Features include locale accessors, translation fallbacks, default values, dirty tracking, and querying translated attributes via an i18n scope.

Tokens
8.8K
Snippets
34
Records
42
Agent score
77%

What's inside Mobility

  1. How to set the Mobility locale

    master

    Mobility has its own locale setting that defaults to I18n.locale but can be set independently. This is useful when you want to show content in one language while the UI is in another.

    Mobility.locale = :fr
    
    # Set locale within a block
    Mobility.with_locale(:ja) do
      Mobility.locale #=> :ja
    end
    # Mobility.locale is back to :fr

    Mobility uses RequestStore to ensure these global variables are reset after every request, making it thread-safe in Rails environments.

    Mobility.with_locale(:ja) do
      # code using Japanese translations
    end
  2. Access translations in specific locales

    master

    While Mobility defaults to the current I18n.locale, you can access specific translations using several methods:

    1. Locale Accessors (Explicit Methods)

    Enable the locale_accessors plugin to generate methods like attribute_locale (e.g., name_en).

    Global Configuration:

    Mobility.configure do
      plugins do
        locale_accessors [:en, :ja]
      end
    end

    Model-level Override:

    class Word < ApplicationRecord
      extend Mobility
      translates :name, locale_accessors: [:en, :ja]
    end

    2. Fallthrough Accessors (Dynamic Methods)

    Enable the fallthrough_accessors plugin to use Ruby's method_missing. This allows you to call name_any_locale without pre-defining them.

    Global Configuration:

    Mobility.configure do
      plugins do
        fallthrough_accessors
      end
    end

    3. Passing Locale to Getter/Setter

    You can pass a locale option directly to the getter or use send for the setter:

    # Getter
    word.name(locale: :en)
    
    # Setter
    word.send(:name=, "mobiliteit", locale: :nl)

    4. Using the Backend directly

    Access the underlying storage via <attribute>_backend:

    word.name_backend.read(:en)
    word.name_backend.write(:en, "foo")
    # Example of locale accessors
    word.name_en = "mobility"
    word.name_en #=> "mobility"
  3. Setup Mobility with Sequel

    master

    Mobility requires Sequel >= 4.0.

    1. Configure Plugins: Ensure you include the sequel plugin in your global configuration:
      plugins do
        backend :key_value
        sequel
      end
    2. Extend Models: You can extend models using extend Mobility (similar to ActiveRecord) or use the mobility plugin:
      class Word < ::Sequel::Model
        plugin :mobility
        translates :name, :meaning
      end
    3. Migrations: Unlike Rails, there is no generator for Sequel. You must create the translation table migrations manually.
    class Word < ::Sequel::Model
      plugin :mobility
      translates :name, :meaning
    end
  4. Configure default values for translations

    master

    The default plugin allows you to return a fallback value if no translation is found for the current locale.

    Global Default:

    Mobility.configure do
      plugins do
        default 'foo'
      end
    end

    Model-level Default:

    class Word < ApplicationRecord
      extend Mobility
      translates :name, default: 'foo'
    end

    Dynamic Defaults (Proc): You can pass a Proc as a default. The proc is called with the model instance as context and receives arguments like attribute, locale, and options:

    translates :name, default: ->(model, attribute, locale, options) { "Default for #{locale}" }

    Overriding at Runtime:

    word.name(default: 'bar')
  5. Configure translation fallbacks

    master

    Fallbacks allow a locale to return a value from another locale if the requested one is missing.

    1. Enable Plugins: You must enable both fallbacks and locale_accessors (for performance/tracking).
      plugins do
        fallbacks
        locale_accessors
      end
    2. Define Fallbacks in Model: Pass a hash to the fallbacks option in translates.
    class Word < ApplicationRecord
      extend Mobility
      translates :name, fallbacks: { de: :ja, fr: :ja }
    end

    In this example, if name is requested in German (:de) or French (:fr) and is missing, Mobility will return the Japanese (:ja) value.

    Disabling Fallbacks:

    • Pass fallback: false to the getter: word.meaning(fallback: false).
    • Pass a specific locale option: word.meaning(locale: :de) (this disables fallbacks for that call).
  6. Setup Mobility with ActiveRecord (Rails)

    master

    Mobility requires ActiveRecord >= 7.0 for Rails integration.

    To set up the default KeyValue backend, run the following generator to create an initializer and the necessary migration for shared translation tables:

    rails generate mobility:install

    Note: If you plan to use a different backend (not the default KeyValue), use the --without_tables option to skip generating the default migration:

    rails generate mobility:install --without_tables

    After running the generator, you can configure plugins in config/initializers/mobility.rb using the Mobility.configure block.

  7. Enable Dirty Tracking for translated attributes

    master

    To track changes to translated attributes (e.g., using attribute_was or changed?), enable the dirty plugin. This requires an ORM plugin (active_record or sequel) to be enabled as well.

    Configuration:

    plugins do
      active_record
      dirty
    end

    Usage: Mobility uses locale suffixes to indicate which locale has changed:

    post.title = "a new title"
    Mobility.with_locale(:ja) { post.title = "新しいタイトル" }
    
    post.changed #=> ["title_en", "title_ja"]
    post.title_was #=> "Introducing Mobility"
    post.previous_changes #=> { "title_en" => [...], "title_ja" => [...] }

    Performance Tip: Always enable locale_accessors for the locales you use with the dirty plugin to avoid the performance penalty of method_missing.

  8. Querying translated attributes

    master

    To query models based on translated values, include the query plugin and an ORM plugin.

    Configuration:

    plugins do
      active_record
      query
    end

    Using the i18n scope

    By default, querying is performed through an i18n scope. This allows you to use standard query methods like find_by, where, pluck, etc.

    # Find by translated title
    Post.i18n.find_by(title: "foo")
    
    # Pluck translated titles
    Post.i18n.pluck(:title)

    Advanced Block-based Querying

    You can pass a block to the i18n method to build complex, backend-independent predicates using Arel-style syntax:

    Post.i18n do
      title.matches("foo").and(content.matches("bar"))
    end

    Using as a Default Scope

    If you want to avoid calling .i18n every time, you can set it as a default scope in your model:

    class Post < ApplicationRecord
      extend Mobility
      translates :title
      default_scope { i18n }
    end
    
    # Now you can query directly
    Post.find_by(title: "Introducing Mobility")
    Post.i18n.find_by(title: "foo", content: "bar")
  9. How to translate attributes in a model

    master

    To enable translations for specific attributes, extend your model with Mobility and call the translates method.

    ActiveRecord Example

    class Word < ApplicationRecord
      extend Mobility
      translates :name, :meaning
    end

    KeyValue Backend Specifics

    When using the KeyValue backend, you must pass the attribute's type (e.g., :string or :text) in the options hash. This tells Mobility which shared translation table to use:

    class Word < ApplicationRecord
      extend Mobility
      translates :name,    type: :string
      translates :meaning, type: :text
    end

    Setting and Getting Values

    Values are automatically scoped to the current I18n.locale or Mobility.locale. You can set and get values like standard attributes:

    word = Word.new
    word.name = "mobility"
    word.name #=> "mobility"
    
    # Changing locale
    I18n.locale = :ja
    word.name #=> nil (if no Japanese translation exists)
    
    word.name = "モビリティ"
    word.name #=> "モビリティ"
  10. Requirements for Sequel::KeyValue backend

    master

    The Mobility::Backends::Sequel::KeyValue backend relies on a caching mechanism to manage translation changes. Because Sequel lacks certain association-building capabilities found in ActiveRecord, the backend uses a cache to track which translations need to be saved or destroyed during the model lifecycle.

    Ensure your configuration explicitly enables the cache to avoid a CacheRequired error.

  11. Configure backend options and types

    master

    When setting up a backend for attributes, you can specify the backend using several formats:

    1. A Symbol or String: Represents the name of the backend (e.g., :json).
    2. A Class: The actual backend class to use.
    3. A two-element Array: [backend_name_or_class, options_hash]. This allows you to pass specific configuration options to that backend.

    If you provide an array, all keys in the options hash must be valid for that specific backend. If invalid keys are provided, an InvalidOptionKey error is raised.

    # Example of configuring a backend with specific options using an array
    # mobility_backend_class, [backend, options]
    # (Note: The exact DSL syntax depends on the model setup block defined in the main documentation)