ask-guests 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4714198941d2dca3569099d2c4f4091bd06adcfa00b74ecbea2d06b1cc9c6977
4
- data.tar.gz: 5bcfc52ebd6b29d135bd923b3392221c9342ba1057d131349c4f7f0b0d2025e8
3
+ metadata.gz: 8bec805817f573f8d9fcbc2b5e7bc7c0e6622aed391b4af6d61f02c3544f1c10
4
+ data.tar.gz: 381594c955f847741546177fc5bb03ead0e71ed6baa62f5adc98277e736fa223
5
5
  SHA512:
6
- metadata.gz: 4c921fa2484da89d6aef5f00bdf4213bef01bab9df1c25dbc167f61e32672a3b68da54f9f58a47bf7c80b2ad6dcb583dfadb0a7b4a5b187507f209d65c6bdf1f
7
- data.tar.gz: 5b8755b9083f995df2adbc6a0679336f343e20d27cb38713f6fab44c2ddc2ebe510878bb08829d8e54fd6cedddab8c88cd1b30377b17294dad23b184d0837740
6
+ metadata.gz: f97bf4428e04bfcb3fb018b76663b825ed46357419ec43ecd5bc2b3c5fe18463fcc8d9f42ecb6d26be59a43404f3d5fa04b1ee2c715983fa971b09c08318d1c5
7
+ data.tar.gz: cdc1be08b4f0ef9c13e93625c16be13fb35198fbd0ed2b9d222fc76cde0989b1fc456804aee2bf8a5cc7016ad3cf4c5304e74833638ed9f898b5a54704aa9c6b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0] — 2026-09-17
4
+
5
+ ### Added
6
+
7
+ - **The table ships with the gem.** `db/migrate/create_ask_guest_sessions.rb` is
8
+ now the one definition of `ask_guest_sessions`, and the install generator copies
9
+ it into the host (`migration_template`, so it lands with a fresh timestamp
10
+ and is skipped if the host already has one). Re-running the installer after
11
+ an upgrade installs any migration a later version adds.
12
+ - **The installer writes the whole app-side wiring:** the migration, the
13
+ `GuestSession` model, and `config/initializers/ask_guests.rb` — then prints
14
+ the controller, claim, and sweep steps. Nothing is left for the host to
15
+ transcribe from a README.
16
+ - **`Ask::Guests::MissingTable`** — the ActiveRecord store checks for its table
17
+ on the first read or write (`#verify_schema!`) and raises this, naming the
18
+ file to copy, instead of surfacing a database error about a relation.
19
+ - The README documents the non-Rails path (plain ActiveRecord/Sinatra) and why
20
+ the host owns the schema rather than the gem creating it at boot.
21
+
22
+ ### Changed
23
+
24
+ - `Ask::Guests::ActiveRecord::SessionStore` raises on a missing table for
25
+ `#create`, `#find`, and `#sweep_stale`; `#verify_schema!` is public if a host
26
+ wants to check at boot.
27
+
3
28
  ## [0.2.0] — 2026-09-16
4
29
 
5
30
  ### Changed
data/README.md CHANGED
@@ -31,14 +31,47 @@ require "ask-guests/rails" # controller concern, auth hook, sweep job
31
31
  gem "ask-guests"
32
32
  ```
33
33
 
34
- In a Rails app, run the installer:
34
+ ### Rails
35
35
 
36
36
  ```bash
37
37
  bin/rails generate ask:guests:install
38
+ bin/rails db:migrate
38
39
  ```
39
40
 
40
- It creates the `guest_sessions` migration and an initializer, and prints
41
- the model and controller wiring.
41
+ The installer copies the `ask_guest_sessions` migration out of the gem, writes
42
+ `app/models/guest_session.rb`, creates `config/initializers/ask_guests.rb`, and
43
+ prints the controller wiring. Then register what claiming transfers (see
44
+ [Claim](#claim)) and schedule the sweep (see [Retention](#retention)).
45
+
46
+ The table ships with the gem (`db/migrate/create_ask_guest_sessions.rb`) rather
47
+ than being created by it. Your app owns its schema: migrations run at deploy
48
+ time, under review, against whichever database you point them at — a gem that
49
+ creates tables at boot would need DDL rights at runtime, race other servers,
50
+ and have to guess which database is yours. Re-running the installer after
51
+ upgrading installs any migration a later version added, and skips the ones you
52
+ already have.
53
+
54
+ ### Anywhere else (plain ActiveRecord, Sinatra, a script)
55
+
56
+ The core needs nothing; the ActiveRecord adapter needs the table:
57
+
58
+ ```ruby
59
+ require "ask-guests/active_record"
60
+
61
+ class GuestSession < Ask::Guests::ActiveRecord::SessionBase
62
+ self.table_name = "ask_guest_sessions"
63
+ end
64
+
65
+ Ask::Guests.configure do |config|
66
+ config.secret = ENV.fetch("GUEST_TOKEN_SECRET")
67
+ config.store = Ask::Guests::ActiveRecord::SessionStore.new(model: GuestSession)
68
+ end
69
+ ```
70
+
71
+ Copy `db/migrate/create_ask_guest_sessions.rb` from the gem into your migrations
72
+ and run them however you migrate. If the table is missing, the store raises
73
+ `Ask::Guests::MissingTable` at the first read or write — with the file to copy
74
+ in the message — rather than letting the database complain about a relation.
42
75
 
43
76
  ## Core usage
44
77
 
@@ -128,7 +161,7 @@ Ask::Guests.sweep.call # destroys unclaimed sessions inactive past retention
128
161
  ```ruby
129
162
  # app/models/guest_session.rb
130
163
  class GuestSession < Ask::Guests::ActiveRecord::SessionBase
131
- self.table_name = "guest_sessions"
164
+ self.table_name = "ask_guest_sessions"
132
165
  has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
133
166
  end
134
167
 
@@ -0,0 +1,30 @@
1
+ # The ask_guest_sessions table.
2
+ #
3
+ # Shipped with the gem so the table has one definition: the install generator
4
+ # copies this file into a Rails app, and an app without Rails copies the same
5
+ # body into whatever migrates its database. A host that edits its copy keeps
6
+ # its own table — but the columns the store reads and writes are these.
7
+ #
8
+ # Named for the gem, like every other table this ecosystem ships: a host may
9
+ # well have its own `guest_sessions`, and the gem has no business claiming it.
10
+ #
11
+ # `t.json` rather than `jsonb` so the same migration runs on PostgreSQL,
12
+ # MySQL, and SQLite; nothing here queries inside the documents, so the jsonb
13
+ # operators buy nothing. Stamped 7.1 because that is the oldest Active Record
14
+ # the gem supports — a newer Rails runs it unchanged.
15
+ class CreateAskGuestSessions < ActiveRecord::Migration[7.1]
16
+ def change
17
+ create_table :ask_guest_sessions do |t|
18
+ t.json :counters, null: false, default: {}
19
+ t.json :metadata, null: false, default: {}
20
+ t.datetime :last_seen_at
21
+ t.datetime :converted_at
22
+ t.references :owner, polymorphic: true
23
+ t.timestamps
24
+ end
25
+
26
+ # Speculative: sessions at risk of being swept.
27
+ add_index :ask_guest_sessions, :converted_at
28
+ add_index :ask_guest_sessions, :last_seen_at
29
+ end
30
+ end
@@ -8,7 +8,7 @@ module Ask
8
8
  # table created by the migration:
9
9
  #
10
10
  # class GuestSession < Ask::Guests::ActiveRecord::SessionBase
11
- # self.table_name = "guest_sessions"
11
+ # self.table_name = "ask_guest_sessions"
12
12
  # has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
13
13
  # end
14
14
  #
@@ -22,6 +22,14 @@ module Ask
22
22
  class SessionBase < ::ActiveRecord::Base
23
23
  self.abstract_class = true
24
24
 
25
+ # The table is named for the gem, the way the rest of the ecosystem's
26
+ # tables are: a host may have its own `guest_sessions`. A subclass that
27
+ # says nothing lands on ask_guest_sessions rather than claiming the
28
+ # generic name; one that sets table_name itself keeps its own.
29
+ def self.table_name_prefix
30
+ "ask_"
31
+ end
32
+
25
33
  # The gem owns these columns' semantics; use the adapter-independent
26
34
  # JSON type so PostgreSQL json/jsonb and SQLite text columns behave
27
35
  # identically (and the model loads without a database connection).
@@ -6,7 +6,7 @@ module Ask
6
6
  # ActiveRecord-backed session store.
7
7
  #
8
8
  # class GuestSession < Ask::Guests::ActiveRecord::SessionBase
9
- # self.table_name = "guest_sessions"
9
+ # self.table_name = "ask_guest_sessions"
10
10
  # has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
11
11
  # end
12
12
  #
@@ -26,6 +26,7 @@ module Ask
26
26
  end
27
27
 
28
28
  def create(attributes = {})
29
+ verify_schema!
29
30
  attributes = attributes.to_h.transform_keys(&:to_sym)
30
31
  now = Guests.configuration.now
31
32
  record = model.new(
@@ -42,6 +43,7 @@ module Ask
42
43
  def find(id)
43
44
  return nil if id.nil?
44
45
 
46
+ verify_schema!
45
47
  model.find_by(id: id)&.to_guest_session
46
48
  end
47
49
 
@@ -69,6 +71,7 @@ module Ask
69
71
  end
70
72
 
71
73
  def sweep_stale(before:)
74
+ verify_schema!
72
75
  destroyed = 0
73
76
  stale_scope(before).find_each do |record|
74
77
  record.destroy
@@ -77,6 +80,16 @@ module Ask
77
80
  destroyed
78
81
  end
79
82
 
83
+ # Raises MissingTable unless the sessions table is there. Called by the
84
+ # operations a host reaches first, so a forgotten migration names
85
+ # itself instead of surfacing as a database error about a relation.
86
+ # Idempotent and cheap: Active Record caches the schema.
87
+ def verify_schema!
88
+ return true if model.table_exists?
89
+
90
+ raise MissingTable, model.table_name
91
+ end
92
+
80
93
  def transaction(&block)
81
94
  model.transaction(&block)
82
95
  end
@@ -11,5 +11,24 @@ module Ask
11
11
  super
12
12
  end
13
13
  end
14
+
15
+ # Raised on the first store operation when the sessions table is not
16
+ # there. The gem ships the table, so a host that has not installed it
17
+ # should hear that — and the command that fixes it — rather than a
18
+ # database complaining about a relation nobody mentioned.
19
+ class MissingTable < Error
20
+ def initialize(table_name = "ask_guest_sessions")
21
+ super(<<~MESSAGE.strip)
22
+ The #{table_name} table does not exist.
23
+
24
+ Install the table the gem ships, then migrate:
25
+ bin/rails generate ask:guests:install
26
+ bin/rails db:migrate
27
+
28
+ Without Rails, copy db/migrate/*_create_ask_guest_sessions.rb from the gem
29
+ into your migrations and run them.
30
+ MESSAGE
31
+ end
32
+ end
14
33
  end
15
34
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Guests
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -1,65 +1,124 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "rails/generators/migration"
4
5
 
5
6
  module Ask
6
7
  module Guests
7
8
  module Generators
9
+ # Installs ask-guests into a Rails app: the ask_guest_sessions migration, the
10
+ # model that maps it, and an initializer to configure. Run it once, then
11
+ # edit what it wrote.
12
+ #
8
13
  # bin/rails generate ask:guests:install
14
+ #
15
+ # The migrations are copied from the gem's own db/migrate, so the table
16
+ # has one definition — the one the store expects. A host that already
17
+ # migrated ask_guest_sessions keeps its file: an identical name is skipped
18
+ # rather than duplicated. Re-running the installer after an upgrade is
19
+ # how a host picks up a migration a later version added.
9
20
  class InstallGenerator < ::Rails::Generators::Base
21
+ include Rails::Generators::Migration
22
+
10
23
  source_root File.expand_path("templates", __dir__)
24
+ # The DDL lives in the gem's db/migrate, not in a second copy here.
25
+ source_paths << File.expand_path("../../../../../db/migrate", __dir__)
26
+
27
+ desc "Creates the ask_guest_sessions migration, the model, and the ask-guests initializer"
28
+
29
+ def self.next_migration_number(_dir)
30
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
31
+ end
32
+
33
+ # The migrations the gem ships, by file name.
34
+ def self.migrations
35
+ Dir[File.expand_path("../../../../../db/migrate/*.rb", __dir__)]
36
+ .map { |path| File.basename(path) }.sort
37
+ end
38
+
39
+ def create_migrations
40
+ self.class.migrations.each do |migration|
41
+ next if installed?(migration) && !options[:force]
11
42
 
12
- desc "Creates the guest_sessions migration and ask-guests initializer"
43
+ migration_template migration, "db/migrate/#{migration.sub(/\A\d+_/, "")}"
44
+ end
45
+ end
46
+
47
+ def create_model
48
+ template "guest_session.rb", "app/models/guest_session.rb"
49
+ end
13
50
 
14
51
  def create_initializer
15
52
  template "initializer.rb", "config/initializers/ask_guests.rb"
16
53
  end
17
54
 
18
- def create_migration
19
- timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
20
- template "migration.rb", "db/migrate/#{timestamp}_create_guest_sessions.rb"
55
+ def check_schema
56
+ if schema_installed?
57
+ say "ask_guest_sessions is in place.", :green
58
+ else
59
+ say "Run bin/rails db:migrate to create ask_guest_sessions.", :yellow
60
+ end
21
61
  end
22
62
 
23
- def show_readme
63
+ def show_next_steps
24
64
  say <<~MESSAGE
25
65
 
26
66
  ask-guests installed.
27
67
 
28
68
  Next steps:
29
69
 
30
- 1. Define the model (app/models/guest_session.rb):
70
+ 1. Register what claiming transfers — one handler per record a
71
+ visitor owns (config/initializers/ask_guests.rb):
31
72
 
32
- class GuestSession < Ask::Guests::ActiveRecord::SessionBase
33
- self.table_name = "guest_sessions"
34
-
35
- # Destroying a session sweeps everything the guest owned:
36
- # has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
73
+ Ask::Guests.claimable(:chats) do |session, owner|
74
+ Chat.where(guest_session_id: session.id)
75
+ .update_all(user_id: owner.id, guest_session_id: nil)
37
76
  end
38
77
 
39
- 2. Register what claiming transfers (config/initializers/ask_guests.rb).
40
-
41
- 3. Allow guests in a controller:
78
+ 2. Let guests in, in the controller that serves them:
42
79
 
43
80
  include Ask::Guests::Rails::Controller
81
+
44
82
  allow_guest_access
45
- before_action :require_visitor
83
+ before_action :require_visitor, only: %i[chat create]
46
84
 
47
- 4. Claim on authentication (Devise example):
85
+ ...and key their records to #current_guest_session.
86
+
87
+ 3. Claim on authentication (Devise example):
48
88
 
49
89
  include Ask::Guests::Rails::ClaimOnAuthentication
50
90
  after_action :claim_guest_session, only: :create
51
91
 
52
- def guest_claim_owner = current_user.account_users.first
92
+ def guest_claim_owner = current_user
53
93
 
54
- 5. Schedule the sweep:
94
+ 4. Sweep abandoned sessions on a schedule (config/recurring.yml
95
+ with Solid Queue):
55
96
 
56
- production:
57
- guests_sweep:
58
- class: "Ask::Guests::Rails::SweepJob"
59
- schedule: every day at 4am
97
+ sweep_guest_sessions:
98
+ command: "Ask::Guests::Rails::SweepJob.perform_now"
99
+ schedule: every day at 4am
60
100
 
61
101
  MESSAGE
62
102
  end
103
+
104
+ private
105
+
106
+ # A migration the host already has is the host's — it may have run
107
+ # already, so the gem does not touch it unless asked with --force.
108
+ def installed?(migration)
109
+ name = migration.sub(/\A\d+_/, "").delete_suffix(".rb")
110
+ self.class.migration_exists?(File.join(destination_root, "db/migrate"), name)
111
+ end
112
+
113
+ # Advisory only: a database that is not there yet (db:create has not
114
+ # run, or the app boots without one) is not this generator's problem.
115
+ def schema_installed?
116
+ return false unless defined?(::ActiveRecord::Base)
117
+
118
+ ::ActiveRecord::Base.connection.table_exists?("ask_guest_sessions")
119
+ rescue ::ActiveRecord::ActiveRecordError
120
+ false
121
+ end
63
122
  end
64
123
  end
65
124
  end
@@ -0,0 +1,16 @@
1
+ require "ask-guests/active_record"
2
+
3
+ # The ask_guest_sessions table, as the store reads and writes it.
4
+ #
5
+ # Nothing about a guest is a User row: a visitor is their own record, which is
6
+ # what lets an abandoned visit be swept and a visitor who signs up keep the
7
+ # threads they already had.
8
+ class GuestSession < Ask::Guests::ActiveRecord::SessionBase
9
+ self.table_name = "ask_guest_sessions"
10
+
11
+ # Everything a guest owns hangs off this row, so sweeping an abandoned
12
+ # session takes those records with it. Add one line per model that carries a
13
+ # guest_session_id — and make sure claiming moves them (see the initializer):
14
+ #
15
+ # has_many :chats, foreign_key: :guest_session_id, dependent: :destroy
16
+ end
@@ -1,42 +1,46 @@
1
- # frozen_string_literal: true
1
+ require "ask-guests/active_record"
2
+ # The controller concern and the sweep job ride with Rails; the store is
3
+ # ActiveRecord's.
4
+ require "ask-guests/rails"
2
5
 
3
- # Guest access configuration (ask-guests).
6
+ # Guest access (ask-guests).
4
7
  #
5
- # Anonymous visitors get a signed-cookie session, metered agent usage, and
6
- # a single-use claim that moves everything they own to a real account when
7
- # they sign up. See https://github.com/ask-rb/ask-guests for the full guide.
8
+ # Anonymous visitors get a signed-cookie session, metered AI usage, and a
9
+ # single-use claim that moves everything they own to a real account when they
10
+ # sign up. See https://github.com/ask-rb/ask-guests for the full guide.
11
+ #
12
+ # Configured on prepare rather than at boot: the store holds the model class,
13
+ # and a reload must not leave it holding the class that was replaced.
14
+ Rails.application.config.to_prepare do
15
+ Ask::Guests.configure do |config|
16
+ # Required: HMAC secret for guest tokens. Rotating it invalidates all
17
+ # outstanding guest cookies (they simply start fresh).
18
+ config.secret = Rails.application.secret_key_base
8
19
 
9
- Ask::Guests.configure do |config|
10
- # Required: HMAC secret for guest tokens. Rotating it invalidates all
11
- # outstanding guest cookies (they simply start fresh).
12
- config.secret = Rails.application.secret_key_base
20
+ # Persistence: the model the install generator wrote over the migration.
21
+ config.store = Ask::Guests::ActiveRecord::SessionStore.new(model: GuestSession)
13
22
 
14
- # Persistence. Define the model first (see the install generator output):
15
- #
16
- # class GuestSession < Ask::Guests::ActiveRecord::SessionBase
17
- # self.table_name = "guest_sessions"
18
- # has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
19
- # end
20
- config.store = Ask::Guests::ActiveRecord::SessionStore.new(model: GuestSession)
23
+ # Usage caps for anonymous visitors, one entry per category. Signing up
24
+ # lifts them because a signed-in request never touches the guest budget.
25
+ # Periods are :day, :week, or :month; counters roll over automatically.
26
+ # Empty means unlimited — appropriate when the host bills the visit some
27
+ # other way, and a cap needs a state to show the visitor when it bites.
28
+ config.categories = {
29
+ turns: {limit: 40, per: :day}
30
+ # tokens: {limit: 10_000, per: :day},
31
+ # uploads: {limit: 5, per: :day},
32
+ # messages: {limit: 100, per: :month}
33
+ }
21
34
 
22
- # Usage caps for anonymous visitors, one entry per category. Signing up
23
- # lifts them because a signed-in request never touches the guest budget.
24
- # Periods are :day, :week, or :month; counters roll over automatically.
25
- config.categories = {
26
- turns: {limit: 40, per: :day}
27
- # tokens: {limit: 10_000, per: :day},
28
- # uploads: {limit: 5, per: :day},
29
- # messages: {limit: 100, per: :month}
30
- }
31
-
32
- # Unclaimed sessions inactive for this long are swept (seconds).
33
- config.retention = 14 * 24 * 60 * 60
35
+ # Unclaimed sessions inactive for this long are swept (seconds).
36
+ config.retention = 14 * 24 * 60 * 60
37
+ end
34
38
 
35
39
  # What claiming transfers. Register one handler per owned model; they run
36
40
  # inside the claim's transaction, exactly once.
37
41
  #
38
- # Ask::Guests.claimable(:work_requests) do |session, owner|
39
- # WorkRequest.where(guest_session_id: session.id)
40
- # .update_all(account_id: owner.account_id, account_user_id: owner.id, guest_session_id: nil)
41
- # end
42
+ # Ask::Guests.claimable(:work_requests) do |session, owner|
43
+ # WorkRequest.where(guest_session_id: session.id)
44
+ # .update_all(account_id: owner.account_id, account_user_id: owner.id, guest_session_id: nil)
45
+ # end
42
46
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-guests
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -93,6 +93,20 @@ dependencies:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '7.1'
96
+ - !ruby/object:Gem::Dependency
97
+ name: railties
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '7.1'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '7.1'
96
110
  description: 'Give anonymous visitors a durable identity with metered AI access: signed
97
111
  guest sessions, per-day turn/token budgets, single-use claiming of records into
98
112
  a real owner on sign-up, and stale-session retention. Framework-agnostic core with
@@ -106,6 +120,7 @@ files:
106
120
  - CHANGELOG.md
107
121
  - LICENSE
108
122
  - README.md
123
+ - db/migrate/20260917120000_create_ask_guest_sessions.rb
109
124
  - lib/ask-guests.rb
110
125
  - lib/ask-guests/active_record.rb
111
126
  - lib/ask-guests/rails.rb
@@ -127,8 +142,8 @@ files:
127
142
  - lib/ask/guests/token.rb
128
143
  - lib/ask/guests/version.rb
129
144
  - lib/generators/ask/guests/install/install_generator.rb
145
+ - lib/generators/ask/guests/install/templates/guest_session.rb
130
146
  - lib/generators/ask/guests/install/templates/initializer.rb
131
- - lib/generators/ask/guests/install/templates/migration.rb
132
147
  homepage: https://github.com/ask-rb/ask-guests
133
148
  licenses:
134
149
  - MIT
@@ -1,16 +0,0 @@
1
- class CreateGuestSessions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
- def change
3
- create_table :guest_sessions do |t|
4
- t.jsonb :counters, null: false, default: {}
5
- t.jsonb :metadata, null: false, default: {}
6
- t.datetime :last_seen_at
7
- t.datetime :converted_at
8
- t.references :owner, polymorphic: true
9
- t.timestamps
10
- end
11
-
12
- # Speculative: sessions at risk of being swept.
13
- add_index :guest_sessions, :converted_at
14
- add_index :guest_sessions, :last_seen_at
15
- end
16
- end