footprinted 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9896445daea0ce69e019da7472258e3c87035ce60245e8941de245edda8d3865
4
+ data.tar.gz: 3b858b7cc413344388f9e9ccacce615e0a0c56a3e461d87cc69204e52c722b04
5
+ SHA512:
6
+ metadata.gz: 60f7ecfda0afd96cbdbc67591d1c6e5687e02bce4d30f8b5f2b3fdaad61d5493332b3d2a6c8183a0fc960d8e78dfc124af7e934cacdc77558ec5eb3f04e6c772
7
+ data.tar.gz: 876c40969d9a9de55dfe8298808dc162519da408f578e20d6f5b66516e9c272696f82c4f232027ec92915c23cb6277dbf1a49e95a18d6e3ee56c072bce378ef2
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2024-09-25
4
+
5
+ - Initial release
6
+ - Added Trackable concern for easy activity tracking
7
+ - Integrated with trackdown gem for IP geolocation
8
+ - Added customizable tracking associations
9
+ - Created install generator for easy setup
10
+ - Added configuration options
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Javi R
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # 👣 `footprinted` - Track geolocated user activity in Rails
2
+
3
+ `footprinted` provides a simple way to track user activity with associated IP addresses and geolocation data in your Rails app.
4
+
5
+ It's good for tracking profile views, downloads, login attempts, or any user interaction where location matters.
6
+
7
+ ## Why
8
+
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:
14
+
15
+ ```ruby
16
+ # First, add this to your User model
17
+ has_trackable :profile_views
18
+
19
+ # Then, track the activity in the controller
20
+ @user.track_profile_view(ip: request.remote_ip)
21
+
22
+ # And finally, analyze the data
23
+ @user.profile_views.group(:country).count
24
+ # => { 'US'=>529, 'UK'=>291, 'CA'=>78... }
25
+ ```
26
+
27
+ That's it! This is all you need for `footprinted` to store the profile view along with the IP's geolocation data.
28
+
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.
33
+
34
+
35
+ ## How it works
36
+
37
+ `footprinted` relies on a `trackable_activities` table, and provides a model concern to interact with it.
38
+
39
+ This model concern allows you to define polymorphic associations to store activity data associated with any model.
40
+
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?"
48
+
49
+ `footprinted` also provides named methods that interact with the `trackable_activities` table to save and query this data.
50
+
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.
52
+
53
+ ## Installation
54
+
55
+ > [!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.
59
+
60
+ After [`trackdown`](https://github.com/rameerez/trackdown) has been installed and configured, add this line to your application's Gemfile:
61
+
62
+ ```ruby
63
+ gem 'footprinted'
64
+ ```
65
+
66
+ And then execute:
67
+
68
+ ```bash
69
+ bundle install
70
+ rails generate footprinted:install
71
+ rails db:migrate
72
+ ```
73
+
74
+ This will create a migration file to create the polymorphic `trackable_activities` table, and migrate the database.
75
+
76
+ ## Usage
77
+
78
+ ### Basic Setup
79
+
80
+ Include the `Footprinted::Model` concern and declare what you want to track:
81
+
82
+ ```ruby
83
+ class User < ApplicationRecord
84
+ include Footprinted::Model
85
+
86
+ # Track a single activity type
87
+ has_trackable :profile_views
88
+
89
+ # Track multiple activity types
90
+ has_trackable :downloads
91
+ has_trackable :login_attempts
92
+ end
93
+ ```
94
+
95
+ ### Recording Activity
96
+
97
+ `footprinted` generates methods for you.
98
+
99
+ For example, the `has_trackable :profile_views` association automatically provides you with a `track_profile_view` method that you can use:
100
+
101
+ ```ruby
102
+ # Basic tracking with IP
103
+ user.track_profile_view(ip: request.remote_ip)
104
+
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
109
+ )
110
+ ```
111
+
112
+ ### Querying Activity
113
+
114
+ #### Basic Queries
115
+
116
+ ```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)
121
+
122
+ # Location queries
123
+ user.profile_views.by_country('US')
124
+ user.profile_views.countries # => ['US', 'UK', 'CA', ...]
125
+
126
+ # Performer queries
127
+ user.profile_views.performed_by(some_user)
128
+ ```
129
+
130
+ ### Advanced Usage
131
+
132
+ Track multiple activity types:
133
+
134
+ ```ruby
135
+ class Resource < ApplicationRecord
136
+ include Footprinted::Model
137
+
138
+ has_trackable :downloads
139
+ has_trackable :previews
140
+ end
141
+
142
+ # Track activities
143
+ product.track_download(ip: request.remote_ip)
144
+ product.track_preview(ip: request.remote_ip)
145
+
146
+ # Query activities
147
+ product.downloads.count
148
+ product.previews.last_days(30)
149
+ product.downloads.between(1.week.ago, Time.current)
150
+ ```
151
+
152
+ Time-based analysis:
153
+
154
+ ```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, ...}
162
+
163
+ # Hourly distribution
164
+ resource.downloads
165
+ .group("HOUR(created_at)")
166
+ .count
167
+ # => {0=>10, 1=>5, 2=>8, ...}
168
+ ```
169
+
170
+ ## Development
171
+
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.
173
+
174
+ To install this gem onto your local machine, run `bundle exec rake install`.
175
+
176
+ ## Contributing
177
+
178
+ 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.
179
+
180
+ ## License
181
+
182
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ ✘ Add index on users t.index ["user_id"], name: "index_trackable_activities_on_user_id" @cancelled(24-09-26 16:59)
2
+ ✔ make user:references optional in migration (activities may not have an associated user) -- or just make it more flexible, like a performer polymorphic association or something @done(24-10-30 01:35)
3
+ ✔ can they have multiple activity types per model? (user: profile_views, downloads, etc) @done(24-09-26 17:28)
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Footprinted
4
+ class Configuration
5
+
6
+ def initialize
7
+ # No configuration options yet
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Footprinted
4
+ module Model
5
+ extend ActiveSupport::Concern
6
+
7
+ # The Footprinted::Model module provides a flexible way to track activities related to any model that includes this concern.
8
+ #
9
+ # Footprinted tracks activities through a polymorphic association: the Footprinted::TrackableActivity model.
10
+ #
11
+ # The Footprinted::Model concern sets up a :trackable_activities association using the
12
+ # Footprinted::TrackableActivity model.
13
+ #
14
+ # It also provides a basic method to track unnamed activities, `track_activity`,
15
+ # which can be used as is (not recommended) or overridden with a custom activity type:
16
+ #
17
+ # Track specific types of activities using the `has_trackable` class method.
18
+ # This method also dynamically defines a method to create activity records for the custom association.
19
+ # For example, `has_trackable :profile_views` generates the `track_profile_view` method.
20
+ #
21
+ # Example:
22
+ # class YourModel < ApplicationRecord
23
+ # include Trackable
24
+ # has_trackable :profile_views
25
+ # end
26
+ #
27
+ # The above will:
28
+ # - Create a `has_many :profile_views` association.
29
+ # - Define a method `track_profile_view` (singular) to create records in `profile_views`.
30
+ #
31
+ #
32
+ # Methods:
33
+ #
34
+ # - has_trackable(association_name): Sets up a custom association for tracking activities.
35
+ # This method dynamically defines a tracking method based on the given association name.
36
+ #
37
+ # - track_activity(ip, user = nil): Default method provided to track activities. It logs
38
+ # the IP address, and optionally, the user involved in the activity. This method can be
39
+ # overridden in the model including this module for custom behavior.
40
+ #
41
+ # Note:
42
+ # The Footprinted::TrackableActivity model must exist and have a polymorphic association set up
43
+ # with the :trackable attribute for this concern to function correctly.
44
+
45
+ included do
46
+ has_many :trackable_activities, as: :trackable, class_name: 'Footprinted::TrackableActivity', dependent: :destroy
47
+ end
48
+
49
+ class_methods do
50
+ # Method to set custom association names
51
+ def has_trackable(association_name)
52
+ track_method_name = "track_#{association_name.to_s.singularize}"
53
+
54
+ has_many association_name, -> { where(activity_type: association_name.to_s.singularize) },
55
+ as: :trackable, class_name: 'Footprinted::TrackableActivity'
56
+
57
+ # Define a custom method for tracking activities of this type
58
+ define_method(track_method_name) do |ip:, performer: nil|
59
+ send(association_name).create!(
60
+ ip: ip,
61
+ performer: performer,
62
+ activity_type: association_name.to_s.singularize
63
+ )
64
+ end
65
+ end
66
+ end
67
+
68
+ # Fallback method for tracking activity. This will be overridden if has_trackable is called.
69
+ def track_activity(ip:, user: nil, activity_type: nil)
70
+ trackable_activities.create(ip: ip, user: user, activity_type: activity_type)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Footprinted
4
+ class Railtie < Rails::Railtie
5
+ initializer "footprinted.initialize" do
6
+ ActiveSupport.on_load(:active_record) do
7
+ extend Footprinted::Model
8
+ end
9
+ end
10
+
11
+ generators do
12
+ require "generators/footprinted/install_generator"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Footprinted
4
+ class TrackableActivity < ActiveRecord::Base
5
+ # Associations
6
+ belongs_to :trackable, polymorphic: true
7
+ belongs_to :performer, polymorphic: true, optional: true
8
+
9
+ # Validations
10
+ validates :ip, presence: true
11
+ validates :activity_type, presence: true
12
+ validates :trackable, presence: true
13
+
14
+ # Callbacks
15
+ before_save :set_geolocation_data
16
+
17
+ # Scopes
18
+ scope :by_activity, ->(type) { where(activity_type: type) }
19
+ scope :by_country, ->(country) { where(country: country) }
20
+ scope :recent, -> { order(created_at: :desc) }
21
+ scope :performed_by, ->(performer) { where(performer: performer) }
22
+ scope :between, ->(start_date, end_date) { where(created_at: start_date..end_date) }
23
+ scope :last_days, ->(days) { where('created_at >= ?', days.days.ago) }
24
+
25
+ # Class methods
26
+ def self.activity_types
27
+ distinct.pluck(:activity_type)
28
+ end
29
+
30
+ def self.countries
31
+ distinct.where.not(country: nil).pluck(:country)
32
+ end
33
+
34
+ private
35
+
36
+ def set_geolocation_data
37
+ return unless ip.present?
38
+
39
+ unless defined?(Trackdown)
40
+ raise Footprinted::Error, "The Trackdown gem is not installed. Please add `gem 'trackdown'` to your Gemfile and follow the setup instructions to configure the gem and download an IP geolocation database."
41
+ end
42
+
43
+ unless Trackdown.database_exists?
44
+ raise Footprinted::Error, "MaxMind IP geolocation database not found. Please follow the Trackdown gem setup instructions to configure the gem and download an IP geolocation database."
45
+ end
46
+
47
+ location = Trackdown.locate(ip.to_s)
48
+ self.country = location.country_code
49
+ self.city = location.city
50
+ rescue => e
51
+ Rails.logger.error "Failed to geolocate IP #{ip}: #{e.message}"
52
+ nil # Don't fail the save if geolocation fails
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Footprinted
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "footprinted/version"
4
+ require_relative "footprinted/configuration"
5
+ require_relative "footprinted/model"
6
+ require_relative "footprinted/trackable_activity"
7
+
8
+ module Footprinted
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+ attr_writer :configuration
13
+ end
14
+
15
+ def self.configuration
16
+ @configuration ||= Configuration.new
17
+ end
18
+
19
+ def self.configure
20
+ yield(configuration)
21
+ end
22
+
23
+ def self.reset
24
+ @configuration = Configuration.new
25
+ end
26
+ end
27
+
28
+ require "footprinted/railtie" if defined?(Rails)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+ require 'rails/generators/active_record'
5
+
6
+ module Footprinted
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path('templates', __dir__)
12
+
13
+ def self.next_migration_number(dir)
14
+ ActiveRecord::Generators::Base.next_migration_number(dir)
15
+ end
16
+
17
+ def create_migration_file
18
+ migration_template 'create_footprinted_trackable_activities.rb.erb', File.join(db_migrate_path, "create_footprinted_trackable_activities.rb")
19
+ end
20
+
21
+ private
22
+
23
+ def migration_version
24
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ class CreateFootprintedTrackableActivities < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
4
+
5
+ create_table :trackable_activities, id: primary_key_type do |t|
6
+ t.inet :ip, null: false
7
+ t.text :country
8
+ t.text :city
9
+ t.references :trackable, polymorphic: true, null: false, type: foreign_key_type, index: true
10
+ t.references :performer, polymorphic: true, type: foreign_key_type, index: true
11
+ t.text :activity_type, null: false
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :trackable_activities, [:trackable_type, :trackable_id, :activity_type]
17
+ add_index :trackable_activities, :activity_type
18
+ add_index :trackable_activities, :country
19
+ end
20
+
21
+ private
22
+
23
+ def primary_and_foreign_key_types
24
+ config = Rails.configuration.generators
25
+ setting = config.options[config.orm][:primary_key_type]
26
+ primary_key_type = setting || :primary_key
27
+ foreign_key_type = setting || :bigint
28
+ [primary_key_type, foreign_key_type]
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Footprinted.configure do |config|
4
+ end
@@ -0,0 +1,4 @@
1
+ module Footprinted
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: footprinted
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - rameerez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: trackdown
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ description: Track user activity with associated IP addresses and geolocation info,
70
+ easily and with minimal setup. It's good for tracking profile views, downloads,
71
+ login attempts, or any user interaction where location matters.
72
+ email:
73
+ - rubygems@rameerez.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - CHANGELOG.md
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - TODO
83
+ - lib/footprinted.rb
84
+ - lib/footprinted/configuration.rb
85
+ - lib/footprinted/model.rb
86
+ - lib/footprinted/railtie.rb
87
+ - lib/footprinted/trackable_activity.rb
88
+ - lib/footprinted/version.rb
89
+ - lib/generators/footprinted/install_generator.rb
90
+ - lib/generators/footprinted/templates/create_footprinted_trackable_activities.rb.erb
91
+ - lib/generators/footprinted/templates/footprinted.rb
92
+ - sig/footprinted.rbs
93
+ homepage: https://github.com/rameerez/footprinted
94
+ licenses:
95
+ - MIT
96
+ metadata:
97
+ allowed_push_host: https://rubygems.org
98
+ homepage_uri: https://github.com/rameerez/footprinted
99
+ source_code_uri: https://github.com/rameerez/footprinted
100
+ changelog_uri: https://github.com/rameerez/footprinted/blob/main/CHANGELOG.md
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 3.0.0
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.5.16
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Track IP-geolocated user activity in your Rails app
120
+ test_files: []