Module: ConcernsOnRails::Models::Storable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/concerns_on_rails/models/storable.rb
Overview
Typed, defaulted, optionally-validated accessors over a single JSON (or serialized-text) column (“store_attribute-lite”). Rails’ native ‘store_accessor` is untyped on every supported version (a form-submitted “true” stays the String “true”), ships no defaults, and exposes no per-key dirty methods — the gap that the store_attribute / jsonb_accessor gems exist to fill. This concern closes it with no extra dependency.
class Account < ApplicationRecord
include ConcernsOnRails::Storable
storable_by :settings,
theme: { type: :string, default: "light", in: %w[light dark] },
notifications: { type: :boolean, default: true },
items_per_page: { type: :integer, default: 25 },
trial_ends_at: { type: :datetime }
storable_by :flags, { beta: { type: :boolean, default: false } }, prefix: :flag
end
account.theme # => "light" (virtual default; nothing stored yet)
account.notifications = "0"
account.notifications # => false (cast, not the String "0")
account.notifications? # => false (boolean keys get a predicate)
account.flag_beta # => false (prefixed accessor)
account.items_per_page_changed? # per-key dirty, computed off the column's _was
account.reset_theme # drop the key so the reader falls back to the default
Per key: ‘type:` (:string default, :integer, :float, :decimal, :boolean, :date, :datetime, :json), `default:` (a value, or a Proc instance_exec’d per read), ‘in:` (an enumerable membership set). The macro is repeatable —repeat calls for the SAME column merge keys; different columns are independent. `prefix:`/`suffix:` rename the generated accessors as `<prefix>_<key>_<suffix>`.
Notes:
* Whole-column dirty: writing one key reassigns (and so dirties) the
entire column. Two requests writing different keys of the same row are
last-write-wins on the whole hash — there is no per-key merge on save.
* nil vs unset: a writer-stored nil (explicit JSON null) reads back as
nil and does NOT fall back to the default; `reset_<key>` removes the
key entirely so the reader resolves the default again.
* :json values are passed through uncast and the reader returns a dup —
reassign (`record.config = record.config.merge("k" => 1)`), don't
mutate in place, or the write is silently lost.
* Read-side casting never raises: corrupt column JSON decodes to {} and
ungarbageable values cast to nil (ActiveModel semantics). :decimal is
stored precision-safe as a String (BigDecimal), :date/:datetime as
ISO8601 strings (datetime in UTC, microsecond precision).
* Reserved option names: passing key specs as keyword arguments means a
key literally named `prefix` or `suffix` would be swallowed by the
affix options — declare those via the positional Hash escape hatch
(`storable_by :col, { prefix: { type: :string } }`).
* Reach for the store_attribute or jsonb_accessor gems when you need
querying into the store, jsonb operators, or store-backed scopes.
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- LABEL =
"ConcernsOnRails::Models::Storable".freeze
- VALID_TYPES =
%i[string integer float decimal boolean date datetime json].freeze
- ALLOWED_SPEC_KEYS =
%i[type default in].freeze
- CASTERS =
Reusable ActiveModel casters for the JSON-native types. :decimal, :date and :datetime round-trip through Strings and are handled explicitly; :json is passed through uncast.
{ string: ActiveModel::Type::String.new, integer: ActiveModel::Type::Integer.new, float: ActiveModel::Type::Float.new, boolean: ActiveModel::Type::Boolean.new, date: ActiveModel::Type::Date.new, datetime: ActiveModel::Type::DateTime.new }.freeze