ruby 49 lines · 7 steps

Storing user settings in a JSON column with Rails

How store_accessor turns a single JSON column into first-class, validated attributes with query scopes.

Explained by highlit
1class User < ApplicationRecord
2 store_accessor :preferences,
3 :theme,
4 :timezone,
5 :locale,
6 :email_digest_frequency,
7 :marketing_emails,
8 :beta_features
9 
10 DIGEST_FREQUENCIES = %w[never daily weekly monthly].freeze
11 THEMES = %w[light dark system].freeze
12 
13 validates :theme, inclusion: { in: THEMES }, allow_nil: true
14 validates :email_digest_frequency, inclusion: { in: DIGEST_FREQUENCIES }, allow_nil: true
15 validate :timezone_must_be_valid
16 
17 def marketing_emails?
18 ActiveModel::Type::Boolean.new.cast(marketing_emails)
19 end
20 
21 def beta_features?
22 ActiveModel::Type::Boolean.new.cast(beta_features)
23 end
24 
25 def resolved_theme
26 theme.presence || "system"
27 end
28 
29 def resolved_timezone
30 timezone.presence || "UTC"
31 end
32 
33 scope :with_beta_features, -> {
34 where("preferences ->> 'beta_features' = 'true'")
35 }
36 
37 scope :digesting, ->(frequency) {
38 where("preferences ->> 'email_digest_frequency' = ?", frequency)
39 }
40 
41 private
42 
43 def timezone_must_be_valid
44 return if timezone.blank?
45 return if ActiveSupport::TimeZone[timezone].present?
46 
47 errors.add(:timezone, "is not a recognized time zone")
48 end
49end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1store_accessor exposes keys inside a JSON column as ordinary reader/writer methods you can validate and query.
  2. 2JSON keys come back as raw strings, so cast booleans explicitly and query with the database's -> operators.
  3. 3Combining accessors, inclusion validations, and defaults keeps a flexible settings blob safe and predictable.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Storing user settings in a JSON column with Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code