Store Attribute

repository·master·Indexed 19 days ago

https://github.com/palkan/store_attribute

An Active Record extension that adds type-casting support to store accessors. It allows developers to define specific types—such as integer, datetime, boolean, and json—for keys stored within a single database column, such as a JSONB field. The library provides the `store_attribute` method for single attributes and integrates with `store` and `store_accessor` for defining typed accessors with optional prefixes, suffixes, and default values.

Tokens
2.4K
Snippets
8
Records
11
Agent score
66%

What's inside store_attribute

  1. Configure default values for store attributes

    master

    You can provide default values for store_attribute using the default option. This follows Rails attribute behavior:

    1. A default value is only populated if no value for the entire store attribute was set (i.e., only when creating a new record).
    2. Default values persist as soon as the record is saved.

    Behavior for missing keys: By default, store_attribute returns the default value even if the record is persisted but the specific attribute name is not present in the store hash. To change this so it returns nil instead, set store_attribute_unset_values_fallback_to_default = false.

    class User < ActiveRecord::Base
      # Returns default even if key is missing in DB
      store_attribute :extra, :expired_at, :date, default: -> { 2.days.from_now }
    end
    
    # To return nil instead of default for missing keys:
    class User < ApplicationRecord
      self.store_attribute_unset_values_fallback_to_default = false
    end
    
    # Or globally in an initializer:
    # StoreAttribute.store_attribute_unset_values_fallback_to_default = false
  2. Install Store Attribute

    master

    Add the store_attribute gem to your Gemfile based on your Rails version:

    • Rails 6.1+: Use ~> 1.0
    • Rails 5+ (including 6): Use ~> 0.8.0
    • Rails 4.2: Use ~> 0.4.0
    # for Rails 6.1+
    gem "store_attribute", "~> 1.0"
    
    # for Rails 5+ (6 is supported)
    gem "store_attribute", "~> 0.8.0"
    
    # for Rails 4.2
    gem "store_attribute", "~> 0.4.0"
  3. How prefix and suffix affect accessor names

    master

    When defining accessors via store or store_accessor, you can use prefix and suffix to modify the generated method names.

    • If prefix is a String or Symbol, it is prepended with an underscore (e.g., prefix: :meta becomes meta_).
    • If prefix is true, it simply prepends the name with an underscore (e.g., name: :version becomes version_).
    • If suffix is a String or Symbol, it is appended with an underscore (e.g., suffix: :val becomes _val).
    • If suffix is true, it simply appends the name with an underscore (e.g., name: :version becomes version_).
  4. Integrate with existing store_accessor or store definitions

    master

    You can define type-casted attributes alongside standard store_accessor or store definitions.

    Using store_accessor:

    class SuperUser < User
      store_accessor :settings, :privileges, login_at: :datetime
    end

    Using store:

    class User < ActiveRecord::Base
      store :settings, accessors: [:color, :homepage, login_at: :datetime], coder: JSON
    end
  5. Use store_attribute to add type-casted accessors

    master

    The store_attribute method adds type-casted accessors to an existing Active Record store. Type casting occurs when writing data through the accessor, updating the store itself, or when the object is loaded from the database.

    Note: If you update the store hash explicitly (e.g., u.settings["key"] = value), the value is not type-casted. Writing through the accessor (e.g., u.key = value) ensures correct data types within the store.

    class MegaUser < User
      store_attribute :settings, :ratio, :integer, limit: 1
      store_attribute :settings, :login_at, :datetime
      store_attribute :settings, :active, :boolean
      store_attribute :settings, :color, :string, default: "red"
      store_attribute :settings, :colors, :json, default: ["red", "blue"]
      store_attribute :settings, :data, :datetime, default: -> { Time.now }
    end
    
    u = MegaUser.new(active: false, login_at: "2015-01-01 00:01", ratio: "63.4608")
    
    u.login_at.is_a?(DateTime) # => true
    u.ratio # => 63
    u.active? # => false
  6. Configure StoreAttribute global options

    master

    You can configure global behavior for the StoreAttribute gem using two module-level attributes. Note that these must be set before any models are loaded (e.g., in an initializer in Rails) to ensure they are applied correctly.

    • store_attribute_unset_values_fallback_to_default: A boolean that determines if unset values in the database should fallback to the defined default value. Defaults to true.
    • store_attribute_register_attributes: A boolean that determines whether attributes are automatically registered. Defaults to false.
    # In a Rails initializer (e.g., config/initializers/store_attribute.rb)
    StoreAttribute.store_attribute_unset_values_fallback_to_default = false
    StoreAttribute.store_attribute_register_attributes = true
  7. Signature of store_attribute

    master

    The store_attribute method follows this signature:

    store_attribute(store_name, name, type, options)

    • store_name: The name of the store (e.g., :settings).
    • name: The name of the accessor to the store.
    • type: A symbol (e.g., :string, :integer, :datetime, :boolean, :json) or a type object.
    • options: (Optional) A hash of cast type options (e.g., precision, limit, scale, default) or regular store_accessor options (e.g., prefix, suffix).
  8. Define a typed store on an ActiveRecord model

    master

    Use the store method to define a new store on your ActiveRecord model. This method allows you to specify a store_name, a coder (e.g., JSON), and a list of accessors with optional type casting.

    To provide type information for specific accessors, pass a hash as the last element of the accessors array where keys are the accessor names and values are the types (e.g., :datetime, :integer, :string).

    class User < ActiveRecord::Base
      store :settings, accessors: [:color, :homepage, login_at: :datetime], coder: JSON
    end
  9. Add typed accessors to an existing store

    master

    If a store is already defined, you can add more accessors to it using store_accessor. This method supports adding both standard accessors and typed accessors.

    To add typed accessors, pass them as keyword arguments where the key is the accessor name and the value is the type.

    Arguments:

    • store_name: The name of the existing store.
    • *keys: An array of accessor names.
    • prefix: (Optional) A prefix for the accessor method name.
    • suffix: (Optional) A suffix for the accessor method name.
    • **typed_keys: A hash mapping accessor names to their types (e.g., login_at: :datetime).
    class SuperUser < User
      store_accessor :settings, :privileges, login_at: :datetime
    end
  10. Define a single typed store attribute

    master

    Use store_attribute to add a specific typed accessor to an existing store. This ensures that type casting occurs whenever you write data through the accessor, update the store, or load the record from the database.

    Note on behavior: If you update the store hash explicitly (e.g., u.settings['ratio'] = "3.14"), the value returned by the accessor will not be type-casted. However, writing through the accessor (e.g., u.ratio = "3.14") will trigger type casting and update the underlying store with the casted value.

    Arguments:

    • store_name: The name of the store.
    • name: The name of the accessor.
    • type: A symbol (like :integer, :string, :datetime, :boolean) or a type object.
    • prefix: (Optional) Method name prefix.
    • suffix: (Optional) Method name suffix.
    • **options: Additional options for the type (e.g., limit:, precision:, scale:).
    class MegaUser < User
      store_attribute :settings, :ratio, :integer, limit: 1
      store_attribute :settings, :login_at, :datetime
    
      store_attribute :extra, :version, :integer, prefix: :meta
    end
    
    # Usage example:
    u = MegaUser.new(ratio: "63.4608")
    u.ratio # => 63
    
    u.meta_version = "1"
    u.meta_version # => 1