footprinted 0.1.0 → 0.2.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: 9896445daea0ce69e019da7472258e3c87035ce60245e8941de245edda8d3865
4
- data.tar.gz: 3b858b7cc413344388f9e9ccacce615e0a0c56a3e461d87cc69204e52c722b04
3
+ metadata.gz: e0a6166d8616c0c1204631e17bc3ecf5c09cbff6fc94a38ee892061f2679b8c2
4
+ data.tar.gz: '0783420c75bf7f3736b0b4fcd84484281353f07d9fb89c1773066d25cb44dab7'
5
5
  SHA512:
6
- metadata.gz: 60f7ecfda0afd96cbdbc67591d1c6e5687e02bce4d30f8b5f2b3fdaad61d5493332b3d2a6c8183a0fc960d8e78dfc124af7e934cacdc77558ec5eb3f04e6c772
7
- data.tar.gz: 876c40969d9a9de55dfe8298808dc162519da408f578e20d6f5b66516e9c272696f82c4f232027ec92915c23cb6277dbf1a49e95a18d6e3ee56c072bce378ef2
6
+ metadata.gz: ff0f0e67acc390309d8d8491f43efa63d001ae58f72768d041b88f5842a231d76a444d129067dad4daffda702a5cd9d377d08be51c87cd9c76d079fb3f937a79
7
+ data.tar.gz: ff2e6a3705a726900cc0c4bca87ec9c07ca635f3a1ff02f67464affce02a9f5d179fca9f33714c1b7cf5ba1f80577770362ed94a0a9f342709f17e672fedf403
data/.simplecov ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ SimpleCov.start do
4
+ formatter SimpleCov::Formatter::SimpleFormatter
5
+
6
+ add_filter "/test/"
7
+
8
+ track_files "{lib,app}/**/*.rb"
9
+
10
+ enable_coverage :branch
11
+
12
+ minimum_coverage line: 80, branch: 65
13
+
14
+ command_name "Job #{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
15
+ end
16
+
17
+ SimpleCov.at_exit do
18
+ SimpleCov.result.format!
19
+ puts "\n" + "=" * 60
20
+ puts "COVERAGE SUMMARY"
21
+ puts "=" * 60
22
+ puts "Line Coverage: #{SimpleCov.result.covered_percent.round(2)}%"
23
+ puts "Branch Coverage: #{SimpleCov.result.coverage_statistics[:branch]&.percent&.round(2) || 'N/A'}%"
24
+ puts "=" * 60
25
+ end
data/Appraisals ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ appraise "rails-7.2" do
4
+ gem "rails", "~> 7.2.0"
5
+ end
6
+
7
+ appraise "rails-8.1" do
8
+ gem "rails", "~> 8.1.2"
9
+ end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0] - 2026-02-08
4
+
5
+ **Full rewrite.** Breaking changes from v0.1.0.
6
+
7
+ - Rename `TrackableActivity` → `Footprint` (new table: `footprints`)
8
+ - Add `event_type` column for categorizing events (replaces multiple associations per type)
9
+ - Add JSONB `metadata` column with GIN index for arbitrary event data
10
+ - Add `performer` polymorphic reference (who triggered the event)
11
+ - Add `occurred_at` timestamp (defaults to `Time.current`)
12
+ - Add extended geo fields: `region`, `continent`, `timezone`, `latitude`, `longitude` (via trackdown 0.3+)
13
+ - Add generic `track(:event_type, ip:)` method for ad-hoc events
14
+ - Add scopes: `by_event`, `by_country`, `recent`, `last_days`, `between`, `performed_by`
15
+ - Add class methods: `.event_types`, `.countries`
16
+ - Add async mode with `Footprinted::TrackJob` (ActiveJob)
17
+ - Require trackdown ~> 0.3 for full geo field support
18
+ - Document JSONB performance at scale, column promotion pattern, and database compatibility
19
+
20
+ ### Breaking changes
21
+
22
+ - Table renamed from `trackable_activities` to `footprints` — run the new generator and migrate
23
+ - Model renamed from `Footprinted::TrackableActivity` to `Footprinted::Footprint`
24
+ - `has_trackable` now creates `track_<singular>(ip:)` methods instead of the old API
25
+
3
26
  ## [0.1.0] - 2024-09-25
4
27
 
5
28
  - Initial release
@@ -7,4 +30,4 @@
7
30
  - Integrated with trackdown gem for IP geolocation
8
31
  - Added customizable tracking associations
9
32
  - Created install generator for easy setup
10
- - Added configuration options
33
+ - Added configuration options
data/README.md CHANGED
@@ -1,178 +1,344 @@
1
- # 👣 `footprinted` - Track geolocated user activity in Rails
1
+ # 👣 `footprinted` - Simple event tracking for Rails apps
2
2
 
3
- `footprinted` provides a simple way to track user activity with associated IP addresses and geolocation data in your Rails app.
3
+ [![Gem Version](https://badge.fury.io/rb/footprinted.svg)](https://badge.fury.io/rb/footprinted) [![Build Status](https://github.com/rameerez/footprinted/workflows/Tests/badge.svg)](https://github.com/rameerez/footprinted/actions)
4
4
 
5
- It's good for tracking profile views, downloads, login attempts, or any user interaction where location matters.
5
+ > [!TIP]
6
+ > **🚀 Ship your next Rails app 10x faster!** I've built **[RailsFast](https://railsfast.com/?ref=footprinted)**, a production-ready Rails boilerplate template that comes with everything you need to launch a software business in days, not weeks. Go [check it out](https://railsfast.com/?ref=footprinted)!
6
7
 
7
- ## Why
8
+ `footprinted` makes it trivial to add event tracking to any Rails model.
8
9
 
9
- Sometimes you need to know where your users are performing certain actions from.
10
-
11
- For example, let's say your users have profiles. Where has a particular profile been viewed from?
12
-
13
- This gem makes it trivial to track and analyze this kind of data:
10
+ Think of a file transfer app like WeTransfer. You may want to track where every file download came from:
14
11
 
15
12
  ```ruby
16
- # First, add this to your User model
17
- has_trackable :profile_views
13
+ # Add to your model
14
+ has_trackable :downloads
18
15
 
19
- # Then, track the activity in the controller
20
- @user.track_profile_view(ip: request.remote_ip)
16
+ # Track events in the controller
17
+ @file.track_download(ip: request.remote_ip, metadata: { version: "2.1.0" })
21
18
 
22
- # And finally, analyze the data
23
- @user.profile_views.group(:country).count
24
- # => { 'US'=>529, 'UK'=>291, 'CA'=>78... }
19
+ # Query the data
20
+ @file.downloads.by_country("US").last_days(30).count
21
+ # => 42
25
22
  ```
26
23
 
27
- That's it! This is all you need for `footprinted` to store the profile view along with the IP's geolocation data.
24
+ In the example above, `footprinted` adds `footprints` to your `File` model, allowing you to easily record event data; and provides you with methods to build dashboards and analytics / business intelligence systems.
28
25
 
29
- > [!NOTE]
30
- > By adding `has_trackable :profile_views` to your model, `footprinted` automatically creates a `profile_views` association and a `track_profile_view` method to your User model.
31
- >
32
- > `footprinted` does all the heavy lifting for you, so you don't need to define any models or associations. Just track and query.
26
+ More use cases:
27
+ - Track login attempts
28
+ - Track profile views in a social app (think: LinkedIn)
29
+ - Track document open events in a file-signing app (think: DocuSign)
30
+ - Track any business-critical operation for enterprise-compliant audit logs
31
+ - Track any interaction where knowing the *where* (IP, geolocation) or *what* (OS, app version, device ID...) matters
33
32
 
33
+ Every event (footprint) in `footprinted` records the IP address, full geolocation data (country, city, region, coordinates, timezone), arbitrary JSONB metadata, and who triggered it — all resolved automatically via [`trackdown`](https://github.com/rameerez/trackdown). `footprinted` allows you to trivially build analytics dashboards and audit logs for all your app events.
34
34
 
35
- ## How it works
35
+ > [!NOTE]
36
+ > By adding `has_trackable :downloads` to your model, `footprinted` automatically creates a `downloads` scoped association and a `track_download` method. No extra models or associations to define. Just track and query.
36
37
 
37
- `footprinted` relies on a `trackable_activities` table, and provides a model concern to interact with it.
38
+ ## Installation
38
39
 
39
- This model concern allows you to define polymorphic associations to store activity data associated with any model.
40
+ Add this to your Gemfile:
40
41
 
41
- For each activity, `footprinted` stores:
42
- - IP address
43
- - Country
44
- - City
45
- - Activity type
46
- - Event timestamp
47
- - Optionally, an associated `performer` record, which could be a `user`, `admin`, or any other model. It answers the question: "who triggered this activity?"
42
+ ```ruby
43
+ gem "footprinted"
44
+ ```
48
45
 
49
- `footprinted` also provides named methods that interact with the `trackable_activities` table to save and query this data.
46
+ Then run:
50
47
 
51
- For example, `has_trackable :profile_views` will generate the `profile_views` association and the `track_profile_view` method. Similarly, `has_trackable :downloads` will generate the `downloads` association and the `track_download` method.
48
+ ```bash
49
+ bundle install
50
+ rails generate footprinted:install
51
+ rails db:migrate
52
+ ```
52
53
 
53
- ## Installation
54
+ This creates the `footprints` table with columns for IP, geolocation fields, event type, JSONB metadata, polymorphic trackable/performer references, and all the necessary indexes.
54
55
 
55
56
  > [!IMPORTANT]
56
- > This gem depends on the [`trackdown`](https://github.com/rameerez/trackdown) gem for locating IPs.
57
- >
58
- > **Start by following the `trackdown` README to install and configure the gem**, and make sure you have a valid installation with a working MaxMind database before continuing – otherwise we won't be able to get any geolocation data from IPs.
57
+ > This gem depends on [`trackdown`](https://github.com/rameerez/trackdown) for IP geolocation. `trackdown` works out of the box with Cloudflare (zero config) and also supports MaxMind. See the [trackdown README](https://github.com/rameerez/trackdown) for setup instructions.
58
+
59
+ ## Quick Start
59
60
 
60
- After [`trackdown`](https://github.com/rameerez/trackdown) has been installed and configured, add this line to your application's Gemfile:
61
+ Include the concern in any model you want to track activity on:
61
62
 
62
63
  ```ruby
63
- gem 'footprinted'
64
+ class Product < ApplicationRecord
65
+ include Footprinted::Model
66
+
67
+ has_trackable :activations
68
+ has_trackable :downloads
69
+ end
64
70
  ```
65
71
 
66
- And then execute:
72
+ Track events in your controller:
67
73
 
68
- ```bash
69
- bundle install
70
- rails generate footprinted:install
71
- rails db:migrate
74
+ ```ruby
75
+ class DownloadsController < ApplicationController
76
+ def create
77
+ @product = Product.find(params[:product_id])
78
+ @product.track_download(ip: request.remote_ip, metadata: { version: params[:version] })
79
+ end
80
+ end
72
81
  ```
73
82
 
74
- This will create a migration file to create the polymorphic `trackable_activities` table, and migrate the database.
83
+ Query the data:
75
84
 
76
- ## Usage
85
+ ```ruby
86
+ @product.downloads.count # => 847
87
+ @product.downloads.by_country("US").count # => 529
88
+ @product.downloads.last_days(7) # recent downloads
89
+ @product.downloads.countries # => ["US", "UK", "CA", ...]
90
+ ```
77
91
 
78
- ### Basic Setup
92
+ ## `has_trackable` DSL
79
93
 
80
- Include the `Footprinted::Model` concern and declare what you want to track:
94
+ Declare one or more trackable event types on any model:
81
95
 
82
96
  ```ruby
83
- class User < ApplicationRecord
97
+ class Resource < ApplicationRecord
84
98
  include Footprinted::Model
85
-
86
- # Track a single activity type
87
- has_trackable :profile_views
88
-
89
- # Track multiple activity types
99
+
90
100
  has_trackable :downloads
91
- has_trackable :login_attempts
101
+ has_trackable :previews
102
+ has_trackable :shares
92
103
  end
93
104
  ```
94
105
 
95
- ### Recording Activity
106
+ Each `has_trackable` call gives you:
96
107
 
97
- `footprinted` generates methods for you.
108
+ - A **scoped association** (e.g., `resource.downloads`) that only returns footprints of that event type
109
+ - A **track method** (e.g., `resource.track_download(ip:)`) that creates footprints with the correct event type
98
110
 
99
- For example, the `has_trackable :profile_views` association automatically provides you with a `track_profile_view` method that you can use:
111
+ The association name is pluralized, and the track method is singularized:
100
112
 
101
- ```ruby
102
- # Basic tracking with IP
103
- user.track_profile_view(ip: request.remote_ip)
113
+ | Declaration | Association | Track method |
114
+ |---|---|---|
115
+ | `has_trackable :profile_views` | `.profile_views` | `.track_profile_view(ip:)` |
116
+ | `has_trackable :downloads` | `.downloads` | `.track_download(ip:)` |
117
+ | `has_trackable :login_attempts` | `.login_attempts` | `.track_login_attempt(ip:)` |
118
+
119
+ ### Track method parameters
104
120
 
105
- # Or track with a performer as well ("who triggered the activity?")
106
- user.track_profile_view(
107
- ip: request.remote_ip,
108
- performer: current_user
121
+ ```ruby
122
+ @resource.track_download(
123
+ ip: request.remote_ip, # Required: the IP address
124
+ request: request, # Optional: passed to Trackdown for better geolocation
125
+ performer: current_user, # Optional: who triggered this activity
126
+ metadata: { browser: "Chrome" },# Optional: arbitrary JSONB metadata
127
+ occurred_at: 2.hours.ago # Optional: defaults to Time.current
109
128
  )
110
129
  ```
111
130
 
112
- ### Querying Activity
131
+ ## Generic `track()` method
113
132
 
114
- #### Basic Queries
133
+ For ad-hoc event types that don't need a dedicated association, use the generic `track` method:
115
134
 
116
135
  ```ruby
117
- # Basic queries
118
- user.profile_views.recent
119
- user.profile_views.last_days(7)
120
- user.profile_views.between(1.week.ago, Time.current)
136
+ @user.track(:signup, ip: request.remote_ip)
137
+ @user.track(:password_reset, ip: request.remote_ip, performer: admin)
138
+ @user.track("api_call", ip: request.remote_ip, metadata: { endpoint: "/users" })
139
+ ```
121
140
 
122
- # Location queries
123
- user.profile_views.by_country('US')
124
- user.profile_views.countries # => ['US', 'UK', 'CA', ...]
141
+ It accepts the same parameters as `track_<event_type>` and works with both symbols and strings. All events tracked this way are accessible through the `footprints` association:
125
142
 
126
- # Performer queries
127
- user.profile_views.performed_by(some_user)
143
+ ```ruby
144
+ @user.footprints.by_event("signup").count
128
145
  ```
129
146
 
130
- ### Advanced Usage
147
+ ## Scopes
131
148
 
132
- Track multiple activity types:
149
+ `footprinted` provides several useful scopes out of the box:
133
150
 
134
151
  ```ruby
135
- class Resource < ApplicationRecord
136
- include Footprinted::Model
137
-
138
- has_trackable :downloads
139
- has_trackable :previews
140
- end
152
+ # Filter by event type
153
+ @user.footprints.by_event("download")
154
+
155
+ # Filter by country code
156
+ @user.footprints.by_country("US")
141
157
 
142
- # Track activities
143
- product.track_download(ip: request.remote_ip)
144
- product.track_preview(ip: request.remote_ip)
158
+ # Order by most recent
159
+ @user.footprints.recent
145
160
 
146
- # Query activities
147
- product.downloads.count
148
- product.previews.last_days(30)
149
- product.downloads.between(1.week.ago, Time.current)
161
+ # Time-based filtering
162
+ @user.footprints.last_days(30)
163
+ @user.footprints.between(1.week.ago, Time.current)
164
+
165
+ # Filter by performer
166
+ @user.footprints.performed_by(some_user)
150
167
  ```
151
168
 
152
- Time-based analysis:
169
+ Scopes are chainable:
153
170
 
154
171
  ```ruby
155
- # Daily activity for the last 30 days
156
- resource.downloads
157
- .where('created_at > ?', 30.days.ago)
158
- .group("DATE(created_at)")
159
- .count
160
- .transform_keys { |k| k.strftime("%Y-%m-%d") }
161
- # => {"2024-03-26" => 5, "2024-03-25" => 3, ...}
172
+ @user.profile_views
173
+ .by_country("US")
174
+ .last_days(7)
175
+ .performed_by(current_user)
176
+ .recent
177
+ ```
178
+
179
+ ### Class methods
180
+
181
+ ```ruby
182
+ # Get all distinct event types
183
+ Footprinted::Footprint.event_types
184
+ # => ["view", "download", "login"]
185
+
186
+ # Get all distinct country codes (excludes nil)
187
+ Footprinted::Footprint.countries
188
+ # => ["US", "UK", "CA", "DE"]
189
+ ```
190
+
191
+ ## JSONB metadata
192
+
193
+ Every footprint can store arbitrary metadata as JSONB. This is great for device info, SDK versions, or any context you want to associate with the event:
194
+
195
+ ```ruby
196
+ @product.track(:activation, ip: request.remote_ip, metadata: {
197
+ device_id: "A1B2C3",
198
+ app_version: "2.1.0",
199
+ platform: "macOS",
200
+ os_version: "15.2",
201
+ sdk_version: "0.4.0",
202
+ locale: "en_US"
203
+ })
204
+ ```
162
205
 
163
- # Hourly distribution
164
- resource.downloads
165
- .group("HOUR(created_at)")
206
+ Query metadata using your database's JSON operators:
207
+
208
+ ```ruby
209
+ # Find activations from macOS
210
+ @product.footprints
211
+ .by_event("activation")
212
+ .where("metadata->>'platform' = ?", "macOS")
213
+
214
+ # Group by app version
215
+ @product.footprints
216
+ .by_event("activation")
217
+ .group("metadata->>'app_version'")
166
218
  .count
167
- # => {0=>10, 1=>5, 2=>8, ...}
219
+ # => { "2.0.0" => 12, "2.1.0" => 45 }
220
+ ```
221
+
222
+ ### Performance at scale
223
+
224
+ The default migration creates a [GIN index](https://www.postgresql.org/docs/current/gin-intro.html) on the `metadata` column. GIN indexes are excellent for **containment queries** (`@>`, `?`, `?|`) but do **not** speed up key extraction queries like `GROUP BY metadata->>'field'` or `COUNT(DISTINCT metadata->>'field')`.
225
+
226
+ For small-to-medium tables (up to hundreds of thousands of rows), JSONB queries work just fine. At larger scale (millions of rows), if you're frequently grouping or counting distinct values on specific metadata keys, you have two options:
227
+
228
+ **Option 1: Expression indexes** — add B-tree indexes on the specific JSONB keys you query most. No schema change needed:
229
+
230
+ ```ruby
231
+ # In a migration in your host app
232
+ add_index :footprints, "(metadata->>'device_id')", name: "idx_footprints_device_id"
233
+ add_index :footprints, "(metadata->>'app_version')", name: "idx_footprints_app_version"
234
+ ```
235
+
236
+ **Option 2: Promote to columns** — for your hottest query paths (e.g., `device_id` for DAU/MAU), add dedicated columns to the `footprints` table in your host app. This gives you proper B-tree indexes and fast `DISTINCT` counts:
237
+
238
+ ```ruby
239
+ # In a migration in your host app
240
+ add_column :footprints, :device_id, :string
241
+ add_column :footprints, :app_version, :string
242
+ add_column :footprints, :platform, :string
243
+
244
+ add_index :footprints, :device_id
245
+ add_index :footprints, :app_version
246
+ ```
247
+
248
+ Then write to both the column and the metadata hash in your tracking code. The gem stays generic; your app adds the columns it needs.
249
+
250
+ > [!TIP]
251
+ > Which metadata keys to promote depends on your use case. A licensing SaaS might promote `device_id` + `app_version`. An e-commerce app might promote `product_id` + `session_id`. A CMS might promote `page_url` + `referrer`. Keep the JSONB for everything else.
252
+
253
+ ### Database compatibility
254
+
255
+ | Feature | PostgreSQL | MySQL 5.7+ | SQLite |
256
+ |---|---|---|---|
257
+ | JSONB `metadata` column | `jsonb` (native, fast) | `json` (native) | `text` (stored as string) |
258
+ | GIN index on `metadata` | Supported | Not supported | Not supported |
259
+ | JSON queries (`->>`, `@>`) | Full support | `JSON_EXTRACT()` syntax | `json_extract()` via extension |
260
+ | Expression indexes | Supported | Supported (generated columns) | Not supported |
261
+
262
+ PostgreSQL is the recommended database for `footprinted`. It has the best JSONB support, GIN indexes for containment queries, and expression indexes for key extraction. MySQL works but uses different JSON syntax. SQLite works for development and testing but stores metadata as text and has limited JSON query support.
263
+
264
+ ## Async mode with ActiveJob
265
+
266
+ For high-traffic endpoints, you can enqueue footprint creation in the background:
267
+
268
+ ```ruby
269
+ # config/initializers/footprinted.rb
270
+ Footprinted.configure do |config|
271
+ config.async = true
272
+ end
273
+ ```
274
+
275
+ When async is enabled, `track` and `track_<event_type>` calls enqueue a `Footprinted::TrackJob` instead of writing to the database immediately. The job serializes all attributes (including metadata as a hash and occurred_at as ISO 8601) and processes them in the background.
276
+
277
+ You need a working ActiveJob backend (Sidekiq, Solid Queue, etc.) for this to work.
278
+
279
+ ## Geolocation via Trackdown
280
+
281
+ `footprinted` automatically resolves geolocation data from IP addresses using the [`trackdown`](https://github.com/rameerez/trackdown) gem. For every footprint, the following fields are populated:
282
+
283
+ | Field | Example |
284
+ |---|---|
285
+ | `country_code` | `"US"` |
286
+ | `country_name` | `"United States"` |
287
+ | `city` | `"San Francisco"` |
288
+ | `region` | `"California"` |
289
+ | `continent` | `"NA"` |
290
+ | `timezone` | `"America/Los_Angeles"` |
291
+ | `latitude` | `37.7749` |
292
+ | `longitude` | `-122.4194` |
293
+
294
+ If geolocation fails (network error, invalid IP, etc.), the footprint is still saved — just without geolocation data. Errors are logged via `Rails.logger`.
295
+
296
+ If you already know the `country_code`, you can set it directly and geolocation will be skipped:
297
+
298
+ ```ruby
299
+ @user.track_profile_view(ip: "1.2.3.4", country_code: "DE")
300
+ ```
301
+
302
+ ## Configuration
303
+
304
+ Create an initializer (the generator does this for you):
305
+
306
+ ```ruby
307
+ # config/initializers/footprinted.rb
308
+ Footprinted.configure do |config|
309
+ # Enqueue footprint creation via ActiveJob (default: false)
310
+ config.async = false
311
+ end
312
+ ```
313
+
314
+ ## Generator
315
+
316
+ The install generator creates two things:
317
+
318
+ 1. A migration for the `footprints` table with all geolocation columns, polymorphic references, JSONB metadata, indexes, and a composite index on `[trackable_type, trackable_id, event_type, occurred_at]`
319
+ 2. An initializer at `config/initializers/footprinted.rb`
320
+
321
+ ```bash
322
+ rails generate footprinted:install
323
+ rails db:migrate
168
324
  ```
169
325
 
170
326
  ## Development
171
327
 
172
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
328
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
173
329
 
174
330
  To install this gem onto your local machine, run `bundle exec rake install`.
175
331
 
332
+ ### Releasing a new version
333
+
334
+ When bumping the version, update all three of these together:
335
+
336
+ 1. `lib/footprinted/version.rb` — the version constant
337
+ 2. `gemfiles/*.gemfile.lock` — run `bundle exec appraisal install` to regenerate
338
+ 3. `test/footprinted/version_test.rb` — the hardcoded version assertion
339
+
340
+ CI will fail if any of these are out of sync.
341
+
176
342
  ## Contributing
177
343
 
178
344
  Bug reports and pull requests are welcome on GitHub at https://github.com/rameerez/footprinted. Our code of conduct is: just be nice and make your mom proud of what you do and post online.
data/Rakefile CHANGED
@@ -1,4 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- task default: %i[]
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Footprinted
4
+ class TrackJob < ActiveJob::Base
5
+ queue_as :default
6
+
7
+ def perform(trackable_type, trackable_id, attributes)
8
+ trackable = trackable_type.constantize.find_by(id: trackable_id)
9
+ return unless trackable
10
+
11
+ attrs = attributes.symbolize_keys
12
+ attrs[:occurred_at] = Time.parse(attrs[:occurred_at]) if attrs[:occurred_at].is_a?(String)
13
+
14
+ trackable.footprints.create!(attrs)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails", "~> 7.2.0"
7
+
8
+ group :development do
9
+ gem "irb"
10
+ gem "rubocop", "~> 1.0"
11
+ gem "rubocop-minitest", "~> 0.35"
12
+ gem "rubocop-performance", "~> 1.0"
13
+ end
14
+
15
+ group :development, :test do
16
+ gem "appraisal"
17
+ gem "minitest", "~> 6.0"
18
+ gem "minitest-mock"
19
+ gem "minitest-reporters", "~> 1.6"
20
+ gem "mocha", "~> 2.0"
21
+ gem "rack-test"
22
+ gem "sqlite3", ">= 2.1"
23
+ gem "ostruct"
24
+ gem "webmock", "~> 3.19"
25
+ gem "simplecov", require: false
26
+ end
27
+
28
+ gemspec path: "../"