├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── issue_template.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── Appraisals ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── app ├── controllers │ ├── .keep │ └── concerns │ │ └── .keep └── models │ ├── .keep │ └── refer │ ├── application_record.rb │ ├── referral.rb │ ├── referral_code.rb │ └── visit.rb ├── bin ├── rails └── rubocop ├── config └── routes.rb ├── db └── migrate │ ├── 20240611180738_create_refer_referrals.rb │ ├── 20240611183349_create_refer_referral_codes.rb │ └── 20240701172643_create_refer_visits.rb ├── gemfiles ├── rails_7.1.gemfile ├── rails_7.1.gemfile.lock ├── rails_7.2.gemfile ├── rails_7.2.gemfile.lock ├── rails_8.0.gemfile ├── rails_8.0.gemfile.lock ├── rails_main.gemfile └── rails_main.gemfile.lock ├── lib ├── generators │ └── refer │ │ ├── install │ │ ├── USAGE │ │ └── install_generator.rb │ │ └── model │ │ ├── USAGE │ │ └── model_generator.rb ├── refer.rb ├── refer │ ├── controller.rb │ ├── engine.rb │ ├── has_referrals.rb │ ├── model.rb │ └── version.rb └── tasks │ └── refer_tasks.rake ├── refer.gemspec └── test ├── dummy ├── Rakefile ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── images │ │ │ └── .keep │ │ └── stylesheets │ │ │ └── application.css │ ├── channels │ │ └── application_cable │ │ │ ├── channel.rb │ │ │ └── connection.rb │ ├── controllers │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ └── referrals_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── jobs │ │ └── application_job.rb │ ├── mailers │ │ └── application_mailer.rb │ ├── models │ │ ├── application_record.rb │ │ ├── concerns │ │ │ └── .keep │ │ └── user.rb │ └── views │ │ ├── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ │ └── pwa │ │ ├── manifest.json.erb │ │ └── service-worker.js ├── bin │ ├── rails │ ├── rake │ └── setup ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── content_security_policy.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ └── permissions_policy.rb │ ├── locales │ │ └── en.yml │ ├── puma.rb │ ├── routes.rb │ └── storage.yml ├── db │ ├── migrate │ │ └── 20240611180748_create_users.rb │ └── schema.rb ├── lib │ └── assets │ │ └── .keep ├── log │ └── .keep └── public │ ├── 404.html │ ├── 406-unsupported-browser.html │ ├── 422.html │ ├── 500.html │ ├── icon.png │ └── icon.svg ├── fixtures ├── files │ └── .keep ├── refer │ ├── referral_codes.yml │ ├── referrals.yml │ └── visits.yml └── users.yml ├── integration ├── .keep └── referral_test.rb ├── models ├── .keep └── refer │ ├── referral_code_test.rb │ ├── referral_test.rb │ └── visit_test.rb ├── refer_test.rb └── test_helper.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [excid3] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Get Help 4 | url: https://github.com/excid3/refer/discussions/new?category=help 5 | about: If you can't get something to work the way you expect, open a question in our discussion forums. 6 | - name: Feature Request 7 | url: https://github.com/excid3/refer/discussions/new?category=ideas 8 | about: 'Suggest any ideas you have using our discussion forums.' 9 | - name: Bug Report 10 | url: https://github.com/excid3/refer/issues/new?body=%3C%21--%20Please%20provide%20all%20of%20the%20information%20requested%20below.%20We%27re%20a%20small%20team%20and%20without%20all%20of%20this%20information%20it%27s%20not%20possible%20for%20us%20to%20help%20and%20your%20bug%20report%20will%20be%20closed.%20--%3E%0A%0A%2A%2AWhat%20version%20of%20Noticed%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v2.0.4%0A%0A%2A%2AWhat%20version%20of%20Rails%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v7.1.1%0A%0A%2A%2ADescribe%20your%20issue%2A%2A%0A%0ADescribe%20the%20problem%20you%27re%20seeing%2C%20any%20important%20steps%20to%20reproduce%20and%20what%20behavior%20you%20expect%20instead. 11 | about: If you've already asked for help with a problem and confirmed something is broken with Noticed itself, create a bug report. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug 3 | about: File a bug/issue 4 | title: '[BUG] ' 5 | labels: Bug, Needs Triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug Report 11 | 12 | **Describe the Bug:** 13 | <!-- A clear and concise description of the bug --> 14 | 15 | **To Reproduce:** 16 | <!-- Steps to reproduce the behavior --> 17 | 18 | 1. Step 1 19 | 2. Step 2 20 | 3. ... 21 | 22 | **Expected Behavior:** 23 | <!-- A clear and concise description of what you expected to happen --> 24 | 25 | **Actual Behavior:** 26 | <!-- A clear and concise description of what actually happened --> 27 | 28 | **Screenshots (if applicable):** 29 | <!-- If applicable, add screenshots to help explain your problem --> 30 | 31 | **Environment:** 32 | - Gem version: <!-- Specify the version of the Noticed gem where the bug occurred --> 33 | - Ruby version: <!-- Specify the version of Ruby you are using --> 34 | - Rails version: <!-- Specify the version of Rails you are using --> 35 | - Operating System: <!-- Specify your operating system --> 36 | 37 | **Additional Context:** 38 | <!-- Add any other context about the problem here --> 39 | 40 | **Possible Fix:** 41 | <!-- If you have suggestions on how to fix the bug, you can provide them here --> 42 | 43 | **Steps to Reproduce with Fix (if available):** 44 | <!-- If you have a fix, outline the steps to reproduce the bug using your fix --> 45 | 46 | **Related Issues:** 47 | <!-- If applicable, reference any related GitHub issues or pull requests --> 48 | 49 | **Labels to Apply:** 50 | <!-- Suggest labels that should be applied to this issue --> 51 | 52 | **Checklist:** 53 | <!-- Make sure all of these items are completed before submitting the issue --> 54 | 55 | - [ ] I have searched for similar issues and couldn't find any 56 | - [ ] I have checked the documentation for relevant information 57 | - [ ] I have included all the required information 58 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Pull Request 2 | 3 | **Summary:** 4 | <!-- Provide a brief summary of the changes in this pull request --> 5 | 6 | **Related Issue:** 7 | <!-- If applicable, reference the GitHub issue that this pull request resolves --> 8 | 9 | **Description:** 10 | <!-- Elaborate on the changes made in this pull request. What motivated these changes? --> 11 | 12 | **Testing:** 13 | <!-- Describe the steps you've taken to test the changes. Include relevant information for other contributors to verify the modifications --> 14 | 15 | **Screenshots (if applicable):** 16 | <!-- Include any relevant screenshots or GIFs that demonstrate the changes --> 17 | 18 | **Checklist:** 19 | <!-- Make sure all of these items are completed before submitting the pull request --> 20 | 21 | - [ ] Code follows the project's coding standards 22 | - [ ] Tests have been added or updated to cover the changes 23 | - [ ] Documentation has been updated (if applicable) 24 | - [ ] All existing tests pass 25 | - [ ] Conforms to the contributing guidelines 26 | 27 | **Additional Notes:** 28 | <!-- Any additional information or notes for the reviewers --> 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ main ] 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up Ruby 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: .ruby-version 19 | bundler-cache: true 20 | 21 | - name: Lint code for consistent style 22 | run: bin/rubocop -f github 23 | 24 | test: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | matrix: 28 | gemfile: 29 | - rails_7.1 30 | - rails_7.2 31 | - rails_8.0 32 | - rails_main 33 | 34 | steps: 35 | - name: Install packages 36 | run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libsqlite3-0 libvips libsqlite3-0 37 | 38 | - name: Checkout code 39 | uses: actions/checkout@v4 40 | 41 | - name: Set up Ruby 42 | uses: ruby/setup-ruby@v1 43 | with: 44 | ruby-version: .ruby-version 45 | bundler-cache: true 46 | 47 | - name: Run tests 48 | env: 49 | RAILS_ENV: test 50 | # REDIS_URL: redis://localhost:6379/0 51 | run: bin/rails db:test:prepare && bin/rails test 52 | 53 | - name: Keep screenshots from failed system tests 54 | uses: actions/upload-artifact@v4 55 | if: failure() 56 | with: 57 | name: screenshots 58 | path: ${{ github.workspace }}/tmp/screenshots 59 | if-no-files-found: ignore 60 | 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /doc/ 3 | /log/*.log 4 | /pkg/ 5 | /tmp/ 6 | /test/dummy/db/*.sqlite3 7 | /test/dummy/db/*.sqlite3-* 8 | /test/dummy/log/*.log 9 | /test/dummy/storage/ 10 | /test/dummy/tmp/ 11 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Omakase Ruby styling for Rails 2 | inherit_gem: { rubocop-rails-omakase: rubocop.yml } 3 | 4 | # Overwrite or add rules to create your own house style 5 | # 6 | # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` 7 | # Layout/SpaceInsideArrayLiteralBrackets: 8 | # Enabled: false 9 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.3 2 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "rails-7.1" do 2 | gem "rails", "~> 7.1.0" 3 | end 4 | 5 | appraise "rails-7.2" do 6 | gem "rails", "~> 7.2.0" 7 | end 8 | 9 | appraise "rails-8.0" do 10 | gem "rails", "~> 8.0.0" 11 | end 12 | 13 | appraise "rails-main" do 14 | gem "rails", github: "rails/rails" 15 | end 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Unreleased 2 | 3 | * Add lazy load hooks for models #59 4 | 5 | ### 1.0.2 6 | 7 | * Ensure self-referrals aren't allowed #47 8 | 9 | ### 1.0.1 10 | 11 | * Use `as:` for polymorphic associations 12 | * Add tests against Rails 7.1, 7.2, 8.0 and Rails main 13 | 14 | ### 1.0.0 15 | 16 | * Require Rails 7.1+ for `normalizes` support 17 | 18 | ### 0.5.3 19 | 20 | * Only set ReferralCode#code if it is blank #20 21 | 22 | ### 0.5.2 23 | 24 | * Move controller constant to lib 25 | 26 | ### 0.5.1 27 | 28 | * Fixes HasReferrals constant lookup 29 | 30 | ### 0.5.0 31 | 32 | * Add `Refer.referral_completed = ->(referral) { }` callback that runs when a referral is marked as completed 33 | * `referral.complete!` does nothing if already completed 34 | 35 | ### 0.4.0 36 | 37 | * Add `completed` scope to `Refer::Referral` 38 | 39 | ### 0.3.0 40 | 41 | * Add visit tracking #5 42 | * Configurable referral cookie overwrites #4 43 | Choose between the original referral code or the most recent referral code to receive the referral 44 | * Fix referral code default generator 45 | 46 | ### 0.2.1 47 | 48 | * Change migrations to use Rails 6.1 version for compatibility 49 | 50 | ### 0.2.0 51 | 52 | * Add `set_referral_cookie` controller method 53 | * Add `rails g refer:install` generator to inject `set_referral_cookie` 54 | 55 | ### 0.1.1 56 | 57 | * Added `dependent: :nullify` so ReferralCodes persist Referral records when deleted. 58 | * Added `dependent: :destroy` so Referrals and ReferralCodes are deleted when users are deleted. 59 | * Fixed missing `referral_codes` association on users 60 | 61 | ### 0.1.0 62 | 63 | * Initial release 64 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in refer.gemspec. 4 | gemspec 5 | 6 | gem "puma" 7 | 8 | gem "sqlite3" 9 | 10 | gem "sprockets-rails" 11 | 12 | # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] 13 | gem "rubocop-rails-omakase", require: false 14 | 15 | # Start debugger with binding.b [https://github.com/ruby/debug] 16 | # gem "debug", ">= 1.0.0" 17 | 18 | gem "appraisal" 19 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | refer (1.0.3) 5 | rails (>= 7.1.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (8.0.2) 11 | actionpack (= 8.0.2) 12 | activesupport (= 8.0.2) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | zeitwerk (~> 2.6) 16 | actionmailbox (8.0.2) 17 | actionpack (= 8.0.2) 18 | activejob (= 8.0.2) 19 | activerecord (= 8.0.2) 20 | activestorage (= 8.0.2) 21 | activesupport (= 8.0.2) 22 | mail (>= 2.8.0) 23 | actionmailer (8.0.2) 24 | actionpack (= 8.0.2) 25 | actionview (= 8.0.2) 26 | activejob (= 8.0.2) 27 | activesupport (= 8.0.2) 28 | mail (>= 2.8.0) 29 | rails-dom-testing (~> 2.2) 30 | actionpack (8.0.2) 31 | actionview (= 8.0.2) 32 | activesupport (= 8.0.2) 33 | nokogiri (>= 1.8.5) 34 | rack (>= 2.2.4) 35 | rack-session (>= 1.0.1) 36 | rack-test (>= 0.6.3) 37 | rails-dom-testing (~> 2.2) 38 | rails-html-sanitizer (~> 1.6) 39 | useragent (~> 0.16) 40 | actiontext (8.0.2) 41 | actionpack (= 8.0.2) 42 | activerecord (= 8.0.2) 43 | activestorage (= 8.0.2) 44 | activesupport (= 8.0.2) 45 | globalid (>= 0.6.0) 46 | nokogiri (>= 1.8.5) 47 | actionview (8.0.2) 48 | activesupport (= 8.0.2) 49 | builder (~> 3.1) 50 | erubi (~> 1.11) 51 | rails-dom-testing (~> 2.2) 52 | rails-html-sanitizer (~> 1.6) 53 | activejob (8.0.2) 54 | activesupport (= 8.0.2) 55 | globalid (>= 0.3.6) 56 | activemodel (8.0.2) 57 | activesupport (= 8.0.2) 58 | activerecord (8.0.2) 59 | activemodel (= 8.0.2) 60 | activesupport (= 8.0.2) 61 | timeout (>= 0.4.0) 62 | activestorage (8.0.2) 63 | actionpack (= 8.0.2) 64 | activejob (= 8.0.2) 65 | activerecord (= 8.0.2) 66 | activesupport (= 8.0.2) 67 | marcel (~> 1.0) 68 | activesupport (8.0.2) 69 | base64 70 | benchmark (>= 0.3) 71 | bigdecimal 72 | concurrent-ruby (~> 1.0, >= 1.3.1) 73 | connection_pool (>= 2.2.5) 74 | drb 75 | i18n (>= 1.6, < 2) 76 | logger (>= 1.4.2) 77 | minitest (>= 5.1) 78 | securerandom (>= 0.3) 79 | tzinfo (~> 2.0, >= 2.0.5) 80 | uri (>= 0.13.1) 81 | appraisal (2.5.0) 82 | bundler 83 | rake 84 | thor (>= 0.14.0) 85 | ast (2.4.3) 86 | base64 (0.2.0) 87 | benchmark (0.4.0) 88 | bigdecimal (3.1.9) 89 | builder (3.3.0) 90 | concurrent-ruby (1.3.5) 91 | connection_pool (2.5.2) 92 | crass (1.0.6) 93 | date (3.4.1) 94 | drb (2.2.1) 95 | erubi (1.13.1) 96 | globalid (1.2.1) 97 | activesupport (>= 6.1) 98 | i18n (1.14.7) 99 | concurrent-ruby (~> 1.0) 100 | io-console (0.8.0) 101 | irb (1.15.2) 102 | pp (>= 0.6.0) 103 | rdoc (>= 4.0.0) 104 | reline (>= 0.4.2) 105 | json (2.11.3) 106 | language_server-protocol (3.17.0.4) 107 | lint_roller (1.1.0) 108 | logger (1.7.0) 109 | loofah (2.24.0) 110 | crass (~> 1.0.2) 111 | nokogiri (>= 1.12.0) 112 | mail (2.8.1) 113 | mini_mime (>= 0.1.1) 114 | net-imap 115 | net-pop 116 | net-smtp 117 | marcel (1.0.4) 118 | mini_mime (1.1.5) 119 | minitest (5.25.5) 120 | net-imap (0.5.7) 121 | date 122 | net-protocol 123 | net-pop (0.1.2) 124 | net-protocol 125 | net-protocol (0.2.2) 126 | timeout 127 | net-smtp (0.5.1) 128 | net-protocol 129 | nio4r (2.7.4) 130 | nokogiri (1.18.8-arm64-darwin) 131 | racc (~> 1.4) 132 | nokogiri (1.18.8-x86_64-darwin) 133 | racc (~> 1.4) 134 | nokogiri (1.18.8-x86_64-linux-gnu) 135 | racc (~> 1.4) 136 | parallel (1.27.0) 137 | parser (3.3.8.0) 138 | ast (~> 2.4.1) 139 | racc 140 | pp (0.6.2) 141 | prettyprint 142 | prettyprint (0.2.0) 143 | prism (1.4.0) 144 | psych (5.2.3) 145 | date 146 | stringio 147 | puma (6.6.0) 148 | nio4r (~> 2.0) 149 | racc (1.8.1) 150 | rack (3.1.13) 151 | rack-session (2.1.0) 152 | base64 (>= 0.1.0) 153 | rack (>= 3.0.0) 154 | rack-test (2.2.0) 155 | rack (>= 1.3) 156 | rackup (2.2.1) 157 | rack (>= 3) 158 | rails (8.0.2) 159 | actioncable (= 8.0.2) 160 | actionmailbox (= 8.0.2) 161 | actionmailer (= 8.0.2) 162 | actionpack (= 8.0.2) 163 | actiontext (= 8.0.2) 164 | actionview (= 8.0.2) 165 | activejob (= 8.0.2) 166 | activemodel (= 8.0.2) 167 | activerecord (= 8.0.2) 168 | activestorage (= 8.0.2) 169 | activesupport (= 8.0.2) 170 | bundler (>= 1.15.0) 171 | railties (= 8.0.2) 172 | rails-dom-testing (2.2.0) 173 | activesupport (>= 5.0.0) 174 | minitest 175 | nokogiri (>= 1.6) 176 | rails-html-sanitizer (1.6.2) 177 | loofah (~> 2.21) 178 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 179 | railties (8.0.2) 180 | actionpack (= 8.0.2) 181 | activesupport (= 8.0.2) 182 | irb (~> 1.13) 183 | rackup (>= 1.0.0) 184 | rake (>= 12.2) 185 | thor (~> 1.0, >= 1.2.2) 186 | zeitwerk (~> 2.6) 187 | rainbow (3.1.1) 188 | rake (13.2.1) 189 | rdoc (6.13.1) 190 | psych (>= 4.0.0) 191 | regexp_parser (2.10.0) 192 | reline (0.6.1) 193 | io-console (~> 0.5) 194 | rubocop (1.75.3) 195 | json (~> 2.3) 196 | language_server-protocol (~> 3.17.0.2) 197 | lint_roller (~> 1.1.0) 198 | parallel (~> 1.10) 199 | parser (>= 3.3.0.2) 200 | rainbow (>= 2.2.2, < 4.0) 201 | regexp_parser (>= 2.9.3, < 3.0) 202 | rubocop-ast (>= 1.44.0, < 2.0) 203 | ruby-progressbar (~> 1.7) 204 | unicode-display_width (>= 2.4.0, < 4.0) 205 | rubocop-ast (1.44.1) 206 | parser (>= 3.3.7.2) 207 | prism (~> 1.4) 208 | rubocop-performance (1.25.0) 209 | lint_roller (~> 1.1) 210 | rubocop (>= 1.75.0, < 2.0) 211 | rubocop-ast (>= 1.38.0, < 2.0) 212 | rubocop-rails (2.31.0) 213 | activesupport (>= 4.2.0) 214 | lint_roller (~> 1.1) 215 | rack (>= 1.1) 216 | rubocop (>= 1.75.0, < 2.0) 217 | rubocop-ast (>= 1.38.0, < 2.0) 218 | rubocop-rails-omakase (1.1.0) 219 | rubocop (>= 1.72) 220 | rubocop-performance (>= 1.24) 221 | rubocop-rails (>= 2.30) 222 | ruby-progressbar (1.13.0) 223 | securerandom (0.4.1) 224 | sprockets (4.2.2) 225 | concurrent-ruby (~> 1.0) 226 | logger 227 | rack (>= 2.2.4, < 4) 228 | sprockets-rails (3.5.2) 229 | actionpack (>= 6.1) 230 | activesupport (>= 6.1) 231 | sprockets (>= 3.0.0) 232 | sqlite3 (2.6.0-arm64-darwin) 233 | sqlite3 (2.6.0-x86_64-darwin) 234 | sqlite3 (2.6.0-x86_64-linux-gnu) 235 | stringio (3.1.7) 236 | thor (1.3.2) 237 | timeout (0.4.3) 238 | tzinfo (2.0.6) 239 | concurrent-ruby (~> 1.0) 240 | unicode-display_width (3.1.4) 241 | unicode-emoji (~> 4.0, >= 4.0.4) 242 | unicode-emoji (4.0.4) 243 | uri (1.0.3) 244 | useragent (0.16.11) 245 | websocket-driver (0.7.7) 246 | base64 247 | websocket-extensions (>= 0.1.0) 248 | websocket-extensions (0.1.5) 249 | zeitwerk (2.7.2) 250 | 251 | PLATFORMS 252 | arm64-darwin 253 | x86_64-darwin-23 254 | x86_64-linux 255 | 256 | DEPENDENCIES 257 | appraisal 258 | puma 259 | refer! 260 | rubocop-rails-omakase 261 | sprockets-rails 262 | sqlite3 263 | 264 | BUNDLED WITH 265 | 2.6.8 266 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Chris Oliver 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Refer 2 | 3 | Referral codes and affiliate links for Ruby on Rails applications. 4 | 5 | ## 📦 Installation 6 | Add this line to your application's Gemfile: 7 | 8 | ```ruby 9 | gem "refer" 10 | ``` 11 | 12 | And then execute: 13 | ```bash 14 | $ bundle 15 | ``` 16 | 17 | Add Refer to your controllers to store referral cookies: 18 | ```bash 19 | bin/rails generate refer:install 20 | ``` 21 | 22 | And add Refer to your model: 23 | ```bash 24 | bin/rails generate refer:model User 25 | bin/rails db:migrate 26 | ``` 27 | 28 | ## 🧑‍💻 Usage 29 | 30 | Refer adds a model to your Rails application for tracking referrals and referral codes. 31 | 32 | To track referrals, you'll need to 33 | 34 | 1. Create a referral code 35 | 2. Set a cookie with the referral code 36 | 3. Create the referral 37 | 4. (Optional) Provide a reward for successful referral 38 | 39 | ##### Create a referral code 40 | 41 | You can create referral codes through the association: 42 | 43 | ```ruby 44 | user.referral_codes.create #=> randomly generated code 45 | user.referral_codes.create(code: "chris") 46 | ``` 47 | 48 | To customize the referral code generator: 49 | 50 | ```ruby 51 | Refer.code_generator = ->(referrer) { [referrer.id, SecureRandom.alphanumeric(8)].join("-") } 52 | #=> generates codes like "1-7frb5fUw" 53 | ``` 54 | 55 | By default, Refer will generate 8 character alphanumeric codes. 56 | 57 | ##### Set a referral cookie 58 | 59 | To track users, we need to stash the referral code in a cookie when present. By default, Refer will look for `?ref=code` and save this in a cookie. 60 | 61 | ```ruby 62 | class ApplicationController < ActionController::Base 63 | set_referral_cookie 64 | end 65 | ``` 66 | 67 | Move `set_referral_cookie` to specific controllers if you'd only like cookies set in certain areas like marketing pages for example. 68 | 69 | ```ruby 70 | class MarketingController < ActionController::Base 71 | set_referral_cookie except: [:about_us] 72 | end 73 | ``` 74 | 75 | You can customize the param name with: 76 | 77 | ```ruby 78 | Refer.param_name = :ref 79 | ``` 80 | 81 | You can customize the cookie name with: 82 | 83 | ```ruby 84 | Refer.cookie_name = :refer_code 85 | ``` 86 | 87 | ##### Refer a user: 88 | 89 | To create a referral, you can run the following 90 | 91 | ```ruby 92 | class RegistrationsController < ApplicationController 93 | def create 94 | @user = User.new(user_params) 95 | if @user.save 96 | refer @user #=> Looks up cookie and attempts referral 97 | redirect_to root_path 98 | else 99 | render :new, status: :unprocessable_entity 100 | end 101 | end 102 | end 103 | ``` 104 | 105 | You can also do this manually: 106 | 107 | ```ruby 108 | Refer.refer(code: "referral_code", referee: user) 109 | ``` 110 | 111 | Refer will make sure the user has not already been referred and create a Referral. 112 | 113 | ##### Check if a user was referred already: 114 | 115 | ```ruby 116 | Refer.referred?(user) 117 | #=> true/false 118 | ``` 119 | 120 | ##### Accessing Referrals 121 | 122 | To access a user's referrals, you can use the `referrals` association: 123 | 124 | ```ruby 125 | user.referrals #=> [Refer::Referral, Refer::Referral] 126 | ``` 127 | 128 | This returns a list of `Refer::Referral` objects. 129 | 130 | ##### Accessing Referral 131 | 132 | To access a user's referral, you can use the `referral` association: 133 | 134 | ```ruby 135 | user.referral #=> Refer::Referral 136 | ``` 137 | 138 | To access a user's referrer, you can use `referrer`: 139 | ```ruby 140 | user.referrer #=> User that referred this User 141 | ``` 142 | ## Refer with Devise 143 | 144 | To use Refer with Devise, you'll need to customize the Devise controller to track the referral after a successful registration. 145 | 146 | We can do this by telling Devise to use a custom controller in the routes and hooking into the `create` action to track the referral. 147 | 148 | ```ruby 149 | # config/routes.rb 150 | devise_for :users, controllers: { registrations: "users/registrations" } 151 | ``` 152 | 153 | ```ruby 154 | # app/controllers/users/registrations_controller.rb 155 | class Users::RegistrationsController < Devise::RegistrationsController 156 | def create 157 | super do 158 | refer resource if resource.persisted? 159 | end 160 | end 161 | end 162 | ``` 163 | 164 | ## Providing Referral Rewards 165 | 166 | There are several common ways of handling rewards for successful referrals: 167 | 168 | * Immediate rewards 169 | When the referral is successfully created, you can immediately credit the referrer with their reward. 170 | 171 | * Reward after user actions 172 | You can check if a user was referred after they complete the action and provide a reward to the referrer. 173 | 174 | * Time-based rewards 175 | To provide a reward X days after a successful referral, you can use a schedule job to check for referrals X days ago and provide rewards to those referrers. 176 | 177 | We recommend keeping records for each reward given to a referral so you can limit rewards. 178 | 179 | ## Customizing Models 180 | 181 | You can add features to Refer's models by using lazy load hooks. 182 | 183 | ```ruby 184 | # config/initializers/refer.rb 185 | ActiveSupport.on_load :refer_referral do 186 | # Add features to Refer::Referral model 187 | end 188 | 189 | ActiveSupport.on_load :refer_referral_code do 190 | # Add features to Refer::ReferralCode model 191 | end 192 | 193 | ActiveSupport.on_load :refer_visit do 194 | # Add features to Refer::Visit model 195 | end 196 | ``` 197 | 198 | ## 🙏 Contributing 199 | If you have an issue you'd like to submit, please do so using the issue tracker in GitHub. In order for us to help you in the best way possible, please be as detailed as you can. 200 | 201 | ## 📝 License 202 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 203 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) 4 | load "rails/tasks/engine.rake" 5 | 6 | load "rails/tasks/statistics.rake" 7 | 8 | require "bundler/gem_tasks" 9 | -------------------------------------------------------------------------------- /app/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/app/controllers/.keep -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/app/models/.keep -------------------------------------------------------------------------------- /app/models/refer/application_record.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | class ApplicationRecord < ActiveRecord::Base 3 | self.abstract_class = true 4 | self.table_name_prefix = "refer_" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/refer/referral.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | class Referral < ApplicationRecord 3 | belongs_to :referrer, polymorphic: true 4 | belongs_to :referee, polymorphic: true 5 | belongs_to :referral_code, optional: true, counter_cache: true 6 | 7 | scope :completed, -> { where.not(completed_at: nil) } 8 | 9 | before_validation do 10 | self.referrer = referral_code&.referrer 11 | end 12 | 13 | validate :ensure_not_self_referral 14 | 15 | def ensure_not_self_referral 16 | errors.add(:base, "Self-referrals are not allowed") if referrer == referee 17 | end 18 | 19 | def complete!(**attributes) 20 | return if completed_at? 21 | 22 | update attributes.with_defaults(completed_at: Time.current) 23 | Refer.referral_completed&.call(self) 24 | end 25 | end 26 | end 27 | 28 | ActiveSupport.run_load_hooks :refer_referral, Refer::Referral 29 | -------------------------------------------------------------------------------- /app/models/refer/referral_code.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | class ReferralCode < ApplicationRecord 3 | belongs_to :referrer, polymorphic: true 4 | has_many :referrals, dependent: :nullify 5 | has_many :visits, dependent: :delete_all 6 | 7 | validates :code, presence: true, uniqueness: true 8 | 9 | before_validation if: -> { !code? && Refer.code_generator } do 10 | self.code = Refer.code_generator.call(referrer) 11 | end 12 | 13 | def to_param 14 | code 15 | end 16 | 17 | def track_visit(request) 18 | visits.from_request(request).save 19 | end 20 | end 21 | end 22 | 23 | ActiveSupport.run_load_hooks :refer_referral_code, Refer::ReferralCode 24 | -------------------------------------------------------------------------------- /app/models/refer/visit.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | class Visit < ApplicationRecord 3 | belongs_to :referral_code, counter_cache: true 4 | 5 | normalizes :ip, with: -> { Refer.mask_ip(_1) } 6 | 7 | def self.from_request(request) 8 | new( 9 | ip: request.ip, 10 | user_agent: request.user_agent, 11 | referrer: request.referrer, 12 | referring_domain: (URI.parse(request.referrer).try(:host) rescue nil) 13 | ) 14 | end 15 | end 16 | end 17 | 18 | ActiveSupport.run_load_hooks :refer_visit, Refer::Visit 19 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails gems 3 | # installed from the root of your application. 4 | 5 | ENGINE_ROOT = File.expand_path("..", __dir__) 6 | ENGINE_PATH = File.expand_path("../lib/refer/engine", __dir__) 7 | APP_PATH = File.expand_path("../test/dummy/config/application", __dir__) 8 | 9 | # Set up gems listed in the Gemfile. 10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 11 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 12 | 13 | require "rails/all" 14 | require "rails/engine/commands" 15 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | end 3 | -------------------------------------------------------------------------------- /db/migrate/20240611180738_create_refer_referrals.rb: -------------------------------------------------------------------------------- 1 | class CreateReferReferrals < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :refer_referrals do |t| 4 | t.belongs_to :referrer, polymorphic: true, null: false 5 | t.belongs_to :referee, polymorphic: true, null: false 6 | t.belongs_to :referral_code 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20240611183349_create_refer_referral_codes.rb: -------------------------------------------------------------------------------- 1 | class CreateReferReferralCodes < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :refer_referral_codes do |t| 4 | t.belongs_to :referrer, polymorphic: true, null: false 5 | t.string :code, null: false, index: { unique: true } 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20240701172643_create_refer_visits.rb: -------------------------------------------------------------------------------- 1 | class CreateReferVisits < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :refer_visits do |t| 4 | t.belongs_to :referral_code, null: false, foreign_key: { to_table: :refer_referral_codes } 5 | t.string :ip 6 | t.text :user_agent 7 | t.text :referrer 8 | t.string :referring_domain 9 | 10 | t.timestamps 11 | end 12 | 13 | add_column :refer_referral_codes, :referrals_count, :integer, default: 0 14 | add_column :refer_referral_codes, :visits_count, :integer, default: 0 15 | add_column :refer_referrals, :completed_at, :datetime 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /gemfiles/rails_7.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "puma" 6 | gem "sqlite3" 7 | gem "sprockets-rails" 8 | gem "rubocop-rails-omakase", require: false 9 | gem "appraisal" 10 | gem "rails", "~> 7.1.0" 11 | 12 | gemspec path: "../" 13 | -------------------------------------------------------------------------------- /gemfiles/rails_7.1.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | refer (1.0.3) 5 | rails (>= 7.1.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (7.1.5.1) 11 | actionpack (= 7.1.5.1) 12 | activesupport (= 7.1.5.1) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | zeitwerk (~> 2.6) 16 | actionmailbox (7.1.5.1) 17 | actionpack (= 7.1.5.1) 18 | activejob (= 7.1.5.1) 19 | activerecord (= 7.1.5.1) 20 | activestorage (= 7.1.5.1) 21 | activesupport (= 7.1.5.1) 22 | mail (>= 2.7.1) 23 | net-imap 24 | net-pop 25 | net-smtp 26 | actionmailer (7.1.5.1) 27 | actionpack (= 7.1.5.1) 28 | actionview (= 7.1.5.1) 29 | activejob (= 7.1.5.1) 30 | activesupport (= 7.1.5.1) 31 | mail (~> 2.5, >= 2.5.4) 32 | net-imap 33 | net-pop 34 | net-smtp 35 | rails-dom-testing (~> 2.2) 36 | actionpack (7.1.5.1) 37 | actionview (= 7.1.5.1) 38 | activesupport (= 7.1.5.1) 39 | nokogiri (>= 1.8.5) 40 | racc 41 | rack (>= 2.2.4) 42 | rack-session (>= 1.0.1) 43 | rack-test (>= 0.6.3) 44 | rails-dom-testing (~> 2.2) 45 | rails-html-sanitizer (~> 1.6) 46 | actiontext (7.1.5.1) 47 | actionpack (= 7.1.5.1) 48 | activerecord (= 7.1.5.1) 49 | activestorage (= 7.1.5.1) 50 | activesupport (= 7.1.5.1) 51 | globalid (>= 0.6.0) 52 | nokogiri (>= 1.8.5) 53 | actionview (7.1.5.1) 54 | activesupport (= 7.1.5.1) 55 | builder (~> 3.1) 56 | erubi (~> 1.11) 57 | rails-dom-testing (~> 2.2) 58 | rails-html-sanitizer (~> 1.6) 59 | activejob (7.1.5.1) 60 | activesupport (= 7.1.5.1) 61 | globalid (>= 0.3.6) 62 | activemodel (7.1.5.1) 63 | activesupport (= 7.1.5.1) 64 | activerecord (7.1.5.1) 65 | activemodel (= 7.1.5.1) 66 | activesupport (= 7.1.5.1) 67 | timeout (>= 0.4.0) 68 | activestorage (7.1.5.1) 69 | actionpack (= 7.1.5.1) 70 | activejob (= 7.1.5.1) 71 | activerecord (= 7.1.5.1) 72 | activesupport (= 7.1.5.1) 73 | marcel (~> 1.0) 74 | activesupport (7.1.5.1) 75 | base64 76 | benchmark (>= 0.3) 77 | bigdecimal 78 | concurrent-ruby (~> 1.0, >= 1.0.2) 79 | connection_pool (>= 2.2.5) 80 | drb 81 | i18n (>= 1.6, < 2) 82 | logger (>= 1.4.2) 83 | minitest (>= 5.1) 84 | mutex_m 85 | securerandom (>= 0.3) 86 | tzinfo (~> 2.0) 87 | appraisal (2.5.0) 88 | bundler 89 | rake 90 | thor (>= 0.14.0) 91 | ast (2.4.3) 92 | base64 (0.2.0) 93 | benchmark (0.4.0) 94 | bigdecimal (3.1.9) 95 | builder (3.3.0) 96 | concurrent-ruby (1.3.5) 97 | connection_pool (2.5.2) 98 | crass (1.0.6) 99 | date (3.4.1) 100 | drb (2.2.1) 101 | erubi (1.13.1) 102 | globalid (1.2.1) 103 | activesupport (>= 6.1) 104 | i18n (1.14.7) 105 | concurrent-ruby (~> 1.0) 106 | io-console (0.8.0) 107 | irb (1.15.2) 108 | pp (>= 0.6.0) 109 | rdoc (>= 4.0.0) 110 | reline (>= 0.4.2) 111 | json (2.11.3) 112 | language_server-protocol (3.17.0.4) 113 | lint_roller (1.1.0) 114 | logger (1.7.0) 115 | loofah (2.24.0) 116 | crass (~> 1.0.2) 117 | nokogiri (>= 1.12.0) 118 | mail (2.8.1) 119 | mini_mime (>= 0.1.1) 120 | net-imap 121 | net-pop 122 | net-smtp 123 | marcel (1.0.4) 124 | mini_mime (1.1.5) 125 | minitest (5.25.5) 126 | mutex_m (0.3.0) 127 | net-imap (0.5.7) 128 | date 129 | net-protocol 130 | net-pop (0.1.2) 131 | net-protocol 132 | net-protocol (0.2.2) 133 | timeout 134 | net-smtp (0.5.1) 135 | net-protocol 136 | nio4r (2.7.4) 137 | nokogiri (1.18.8-arm64-darwin) 138 | racc (~> 1.4) 139 | parallel (1.27.0) 140 | parser (3.3.8.0) 141 | ast (~> 2.4.1) 142 | racc 143 | pp (0.6.2) 144 | prettyprint 145 | prettyprint (0.2.0) 146 | prism (1.4.0) 147 | psych (5.2.3) 148 | date 149 | stringio 150 | puma (6.6.0) 151 | nio4r (~> 2.0) 152 | racc (1.8.1) 153 | rack (3.1.13) 154 | rack-session (2.1.0) 155 | base64 (>= 0.1.0) 156 | rack (>= 3.0.0) 157 | rack-test (2.2.0) 158 | rack (>= 1.3) 159 | rackup (2.2.1) 160 | rack (>= 3) 161 | rails (7.1.5.1) 162 | actioncable (= 7.1.5.1) 163 | actionmailbox (= 7.1.5.1) 164 | actionmailer (= 7.1.5.1) 165 | actionpack (= 7.1.5.1) 166 | actiontext (= 7.1.5.1) 167 | actionview (= 7.1.5.1) 168 | activejob (= 7.1.5.1) 169 | activemodel (= 7.1.5.1) 170 | activerecord (= 7.1.5.1) 171 | activestorage (= 7.1.5.1) 172 | activesupport (= 7.1.5.1) 173 | bundler (>= 1.15.0) 174 | railties (= 7.1.5.1) 175 | rails-dom-testing (2.2.0) 176 | activesupport (>= 5.0.0) 177 | minitest 178 | nokogiri (>= 1.6) 179 | rails-html-sanitizer (1.6.2) 180 | loofah (~> 2.21) 181 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 182 | railties (7.1.5.1) 183 | actionpack (= 7.1.5.1) 184 | activesupport (= 7.1.5.1) 185 | irb 186 | rackup (>= 1.0.0) 187 | rake (>= 12.2) 188 | thor (~> 1.0, >= 1.2.2) 189 | zeitwerk (~> 2.6) 190 | rainbow (3.1.1) 191 | rake (13.2.1) 192 | rdoc (6.13.1) 193 | psych (>= 4.0.0) 194 | regexp_parser (2.10.0) 195 | reline (0.6.1) 196 | io-console (~> 0.5) 197 | rubocop (1.75.3) 198 | json (~> 2.3) 199 | language_server-protocol (~> 3.17.0.2) 200 | lint_roller (~> 1.1.0) 201 | parallel (~> 1.10) 202 | parser (>= 3.3.0.2) 203 | rainbow (>= 2.2.2, < 4.0) 204 | regexp_parser (>= 2.9.3, < 3.0) 205 | rubocop-ast (>= 1.44.0, < 2.0) 206 | ruby-progressbar (~> 1.7) 207 | unicode-display_width (>= 2.4.0, < 4.0) 208 | rubocop-ast (1.44.1) 209 | parser (>= 3.3.7.2) 210 | prism (~> 1.4) 211 | rubocop-performance (1.25.0) 212 | lint_roller (~> 1.1) 213 | rubocop (>= 1.75.0, < 2.0) 214 | rubocop-ast (>= 1.38.0, < 2.0) 215 | rubocop-rails (2.31.0) 216 | activesupport (>= 4.2.0) 217 | lint_roller (~> 1.1) 218 | rack (>= 1.1) 219 | rubocop (>= 1.75.0, < 2.0) 220 | rubocop-ast (>= 1.38.0, < 2.0) 221 | rubocop-rails-omakase (1.1.0) 222 | rubocop (>= 1.72) 223 | rubocop-performance (>= 1.24) 224 | rubocop-rails (>= 2.30) 225 | ruby-progressbar (1.13.0) 226 | securerandom (0.4.1) 227 | sprockets (4.2.2) 228 | concurrent-ruby (~> 1.0) 229 | logger 230 | rack (>= 2.2.4, < 4) 231 | sprockets-rails (3.5.2) 232 | actionpack (>= 6.1) 233 | activesupport (>= 6.1) 234 | sprockets (>= 3.0.0) 235 | sqlite3 (2.6.0-arm64-darwin) 236 | stringio (3.1.7) 237 | thor (1.3.2) 238 | timeout (0.4.3) 239 | tzinfo (2.0.6) 240 | concurrent-ruby (~> 1.0) 241 | unicode-display_width (3.1.4) 242 | unicode-emoji (~> 4.0, >= 4.0.4) 243 | unicode-emoji (4.0.4) 244 | websocket-driver (0.7.7) 245 | base64 246 | websocket-extensions (>= 0.1.0) 247 | websocket-extensions (0.1.5) 248 | zeitwerk (2.7.2) 249 | 250 | PLATFORMS 251 | arm64-darwin 252 | 253 | DEPENDENCIES 254 | appraisal 255 | puma 256 | rails (~> 7.1.0) 257 | refer! 258 | rubocop-rails-omakase 259 | sprockets-rails 260 | sqlite3 261 | 262 | BUNDLED WITH 263 | 2.5.18 264 | -------------------------------------------------------------------------------- /gemfiles/rails_7.2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "puma" 6 | gem "sqlite3" 7 | gem "sprockets-rails" 8 | gem "rubocop-rails-omakase", require: false 9 | gem "appraisal" 10 | gem "rails", "~> 7.2.0" 11 | 12 | gemspec path: "../" 13 | -------------------------------------------------------------------------------- /gemfiles/rails_7.2.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | refer (1.0.3) 5 | rails (>= 7.1.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (7.2.2.1) 11 | actionpack (= 7.2.2.1) 12 | activesupport (= 7.2.2.1) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | zeitwerk (~> 2.6) 16 | actionmailbox (7.2.2.1) 17 | actionpack (= 7.2.2.1) 18 | activejob (= 7.2.2.1) 19 | activerecord (= 7.2.2.1) 20 | activestorage (= 7.2.2.1) 21 | activesupport (= 7.2.2.1) 22 | mail (>= 2.8.0) 23 | actionmailer (7.2.2.1) 24 | actionpack (= 7.2.2.1) 25 | actionview (= 7.2.2.1) 26 | activejob (= 7.2.2.1) 27 | activesupport (= 7.2.2.1) 28 | mail (>= 2.8.0) 29 | rails-dom-testing (~> 2.2) 30 | actionpack (7.2.2.1) 31 | actionview (= 7.2.2.1) 32 | activesupport (= 7.2.2.1) 33 | nokogiri (>= 1.8.5) 34 | racc 35 | rack (>= 2.2.4, < 3.2) 36 | rack-session (>= 1.0.1) 37 | rack-test (>= 0.6.3) 38 | rails-dom-testing (~> 2.2) 39 | rails-html-sanitizer (~> 1.6) 40 | useragent (~> 0.16) 41 | actiontext (7.2.2.1) 42 | actionpack (= 7.2.2.1) 43 | activerecord (= 7.2.2.1) 44 | activestorage (= 7.2.2.1) 45 | activesupport (= 7.2.2.1) 46 | globalid (>= 0.6.0) 47 | nokogiri (>= 1.8.5) 48 | actionview (7.2.2.1) 49 | activesupport (= 7.2.2.1) 50 | builder (~> 3.1) 51 | erubi (~> 1.11) 52 | rails-dom-testing (~> 2.2) 53 | rails-html-sanitizer (~> 1.6) 54 | activejob (7.2.2.1) 55 | activesupport (= 7.2.2.1) 56 | globalid (>= 0.3.6) 57 | activemodel (7.2.2.1) 58 | activesupport (= 7.2.2.1) 59 | activerecord (7.2.2.1) 60 | activemodel (= 7.2.2.1) 61 | activesupport (= 7.2.2.1) 62 | timeout (>= 0.4.0) 63 | activestorage (7.2.2.1) 64 | actionpack (= 7.2.2.1) 65 | activejob (= 7.2.2.1) 66 | activerecord (= 7.2.2.1) 67 | activesupport (= 7.2.2.1) 68 | marcel (~> 1.0) 69 | activesupport (7.2.2.1) 70 | base64 71 | benchmark (>= 0.3) 72 | bigdecimal 73 | concurrent-ruby (~> 1.0, >= 1.3.1) 74 | connection_pool (>= 2.2.5) 75 | drb 76 | i18n (>= 1.6, < 2) 77 | logger (>= 1.4.2) 78 | minitest (>= 5.1) 79 | securerandom (>= 0.3) 80 | tzinfo (~> 2.0, >= 2.0.5) 81 | appraisal (2.5.0) 82 | bundler 83 | rake 84 | thor (>= 0.14.0) 85 | ast (2.4.3) 86 | base64 (0.2.0) 87 | benchmark (0.4.0) 88 | bigdecimal (3.1.9) 89 | builder (3.3.0) 90 | concurrent-ruby (1.3.5) 91 | connection_pool (2.5.2) 92 | crass (1.0.6) 93 | date (3.4.1) 94 | drb (2.2.1) 95 | erubi (1.13.1) 96 | globalid (1.2.1) 97 | activesupport (>= 6.1) 98 | i18n (1.14.7) 99 | concurrent-ruby (~> 1.0) 100 | io-console (0.8.0) 101 | irb (1.15.2) 102 | pp (>= 0.6.0) 103 | rdoc (>= 4.0.0) 104 | reline (>= 0.4.2) 105 | json (2.11.3) 106 | language_server-protocol (3.17.0.4) 107 | lint_roller (1.1.0) 108 | logger (1.7.0) 109 | loofah (2.24.0) 110 | crass (~> 1.0.2) 111 | nokogiri (>= 1.12.0) 112 | mail (2.8.1) 113 | mini_mime (>= 0.1.1) 114 | net-imap 115 | net-pop 116 | net-smtp 117 | marcel (1.0.4) 118 | mini_mime (1.1.5) 119 | minitest (5.25.5) 120 | net-imap (0.5.7) 121 | date 122 | net-protocol 123 | net-pop (0.1.2) 124 | net-protocol 125 | net-protocol (0.2.2) 126 | timeout 127 | net-smtp (0.5.1) 128 | net-protocol 129 | nio4r (2.7.4) 130 | nokogiri (1.18.8-arm64-darwin) 131 | racc (~> 1.4) 132 | parallel (1.27.0) 133 | parser (3.3.8.0) 134 | ast (~> 2.4.1) 135 | racc 136 | pp (0.6.2) 137 | prettyprint 138 | prettyprint (0.2.0) 139 | prism (1.4.0) 140 | psych (5.2.3) 141 | date 142 | stringio 143 | puma (6.6.0) 144 | nio4r (~> 2.0) 145 | racc (1.8.1) 146 | rack (3.1.13) 147 | rack-session (2.1.0) 148 | base64 (>= 0.1.0) 149 | rack (>= 3.0.0) 150 | rack-test (2.2.0) 151 | rack (>= 1.3) 152 | rackup (2.2.1) 153 | rack (>= 3) 154 | rails (7.2.2.1) 155 | actioncable (= 7.2.2.1) 156 | actionmailbox (= 7.2.2.1) 157 | actionmailer (= 7.2.2.1) 158 | actionpack (= 7.2.2.1) 159 | actiontext (= 7.2.2.1) 160 | actionview (= 7.2.2.1) 161 | activejob (= 7.2.2.1) 162 | activemodel (= 7.2.2.1) 163 | activerecord (= 7.2.2.1) 164 | activestorage (= 7.2.2.1) 165 | activesupport (= 7.2.2.1) 166 | bundler (>= 1.15.0) 167 | railties (= 7.2.2.1) 168 | rails-dom-testing (2.2.0) 169 | activesupport (>= 5.0.0) 170 | minitest 171 | nokogiri (>= 1.6) 172 | rails-html-sanitizer (1.6.2) 173 | loofah (~> 2.21) 174 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 175 | railties (7.2.2.1) 176 | actionpack (= 7.2.2.1) 177 | activesupport (= 7.2.2.1) 178 | irb (~> 1.13) 179 | rackup (>= 1.0.0) 180 | rake (>= 12.2) 181 | thor (~> 1.0, >= 1.2.2) 182 | zeitwerk (~> 2.6) 183 | rainbow (3.1.1) 184 | rake (13.2.1) 185 | rdoc (6.13.1) 186 | psych (>= 4.0.0) 187 | regexp_parser (2.10.0) 188 | reline (0.6.1) 189 | io-console (~> 0.5) 190 | rubocop (1.75.3) 191 | json (~> 2.3) 192 | language_server-protocol (~> 3.17.0.2) 193 | lint_roller (~> 1.1.0) 194 | parallel (~> 1.10) 195 | parser (>= 3.3.0.2) 196 | rainbow (>= 2.2.2, < 4.0) 197 | regexp_parser (>= 2.9.3, < 3.0) 198 | rubocop-ast (>= 1.44.0, < 2.0) 199 | ruby-progressbar (~> 1.7) 200 | unicode-display_width (>= 2.4.0, < 4.0) 201 | rubocop-ast (1.44.1) 202 | parser (>= 3.3.7.2) 203 | prism (~> 1.4) 204 | rubocop-performance (1.25.0) 205 | lint_roller (~> 1.1) 206 | rubocop (>= 1.75.0, < 2.0) 207 | rubocop-ast (>= 1.38.0, < 2.0) 208 | rubocop-rails (2.31.0) 209 | activesupport (>= 4.2.0) 210 | lint_roller (~> 1.1) 211 | rack (>= 1.1) 212 | rubocop (>= 1.75.0, < 2.0) 213 | rubocop-ast (>= 1.38.0, < 2.0) 214 | rubocop-rails-omakase (1.1.0) 215 | rubocop (>= 1.72) 216 | rubocop-performance (>= 1.24) 217 | rubocop-rails (>= 2.30) 218 | ruby-progressbar (1.13.0) 219 | securerandom (0.4.1) 220 | sprockets (4.2.2) 221 | concurrent-ruby (~> 1.0) 222 | logger 223 | rack (>= 2.2.4, < 4) 224 | sprockets-rails (3.5.2) 225 | actionpack (>= 6.1) 226 | activesupport (>= 6.1) 227 | sprockets (>= 3.0.0) 228 | sqlite3 (2.6.0-arm64-darwin) 229 | stringio (3.1.7) 230 | thor (1.3.2) 231 | timeout (0.4.3) 232 | tzinfo (2.0.6) 233 | concurrent-ruby (~> 1.0) 234 | unicode-display_width (3.1.4) 235 | unicode-emoji (~> 4.0, >= 4.0.4) 236 | unicode-emoji (4.0.4) 237 | useragent (0.16.11) 238 | websocket-driver (0.7.7) 239 | base64 240 | websocket-extensions (>= 0.1.0) 241 | websocket-extensions (0.1.5) 242 | zeitwerk (2.7.2) 243 | 244 | PLATFORMS 245 | arm64-darwin 246 | 247 | DEPENDENCIES 248 | appraisal 249 | puma 250 | rails (~> 7.2.0) 251 | refer! 252 | rubocop-rails-omakase 253 | sprockets-rails 254 | sqlite3 255 | 256 | BUNDLED WITH 257 | 2.5.18 258 | -------------------------------------------------------------------------------- /gemfiles/rails_8.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "puma" 6 | gem "sqlite3" 7 | gem "sprockets-rails" 8 | gem "rubocop-rails-omakase", require: false 9 | gem "appraisal" 10 | gem "rails", "~> 8.0.0" 11 | 12 | gemspec path: "../" 13 | -------------------------------------------------------------------------------- /gemfiles/rails_8.0.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | refer (1.0.3) 5 | rails (>= 7.1.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (8.0.2) 11 | actionpack (= 8.0.2) 12 | activesupport (= 8.0.2) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | zeitwerk (~> 2.6) 16 | actionmailbox (8.0.2) 17 | actionpack (= 8.0.2) 18 | activejob (= 8.0.2) 19 | activerecord (= 8.0.2) 20 | activestorage (= 8.0.2) 21 | activesupport (= 8.0.2) 22 | mail (>= 2.8.0) 23 | actionmailer (8.0.2) 24 | actionpack (= 8.0.2) 25 | actionview (= 8.0.2) 26 | activejob (= 8.0.2) 27 | activesupport (= 8.0.2) 28 | mail (>= 2.8.0) 29 | rails-dom-testing (~> 2.2) 30 | actionpack (8.0.2) 31 | actionview (= 8.0.2) 32 | activesupport (= 8.0.2) 33 | nokogiri (>= 1.8.5) 34 | rack (>= 2.2.4) 35 | rack-session (>= 1.0.1) 36 | rack-test (>= 0.6.3) 37 | rails-dom-testing (~> 2.2) 38 | rails-html-sanitizer (~> 1.6) 39 | useragent (~> 0.16) 40 | actiontext (8.0.2) 41 | actionpack (= 8.0.2) 42 | activerecord (= 8.0.2) 43 | activestorage (= 8.0.2) 44 | activesupport (= 8.0.2) 45 | globalid (>= 0.6.0) 46 | nokogiri (>= 1.8.5) 47 | actionview (8.0.2) 48 | activesupport (= 8.0.2) 49 | builder (~> 3.1) 50 | erubi (~> 1.11) 51 | rails-dom-testing (~> 2.2) 52 | rails-html-sanitizer (~> 1.6) 53 | activejob (8.0.2) 54 | activesupport (= 8.0.2) 55 | globalid (>= 0.3.6) 56 | activemodel (8.0.2) 57 | activesupport (= 8.0.2) 58 | activerecord (8.0.2) 59 | activemodel (= 8.0.2) 60 | activesupport (= 8.0.2) 61 | timeout (>= 0.4.0) 62 | activestorage (8.0.2) 63 | actionpack (= 8.0.2) 64 | activejob (= 8.0.2) 65 | activerecord (= 8.0.2) 66 | activesupport (= 8.0.2) 67 | marcel (~> 1.0) 68 | activesupport (8.0.2) 69 | base64 70 | benchmark (>= 0.3) 71 | bigdecimal 72 | concurrent-ruby (~> 1.0, >= 1.3.1) 73 | connection_pool (>= 2.2.5) 74 | drb 75 | i18n (>= 1.6, < 2) 76 | logger (>= 1.4.2) 77 | minitest (>= 5.1) 78 | securerandom (>= 0.3) 79 | tzinfo (~> 2.0, >= 2.0.5) 80 | uri (>= 0.13.1) 81 | appraisal (2.5.0) 82 | bundler 83 | rake 84 | thor (>= 0.14.0) 85 | ast (2.4.3) 86 | base64 (0.2.0) 87 | benchmark (0.4.0) 88 | bigdecimal (3.1.9) 89 | builder (3.3.0) 90 | concurrent-ruby (1.3.5) 91 | connection_pool (2.5.2) 92 | crass (1.0.6) 93 | date (3.4.1) 94 | drb (2.2.1) 95 | erubi (1.13.1) 96 | globalid (1.2.1) 97 | activesupport (>= 6.1) 98 | i18n (1.14.7) 99 | concurrent-ruby (~> 1.0) 100 | io-console (0.8.0) 101 | irb (1.15.2) 102 | pp (>= 0.6.0) 103 | rdoc (>= 4.0.0) 104 | reline (>= 0.4.2) 105 | json (2.11.3) 106 | language_server-protocol (3.17.0.4) 107 | lint_roller (1.1.0) 108 | logger (1.7.0) 109 | loofah (2.24.0) 110 | crass (~> 1.0.2) 111 | nokogiri (>= 1.12.0) 112 | mail (2.8.1) 113 | mini_mime (>= 0.1.1) 114 | net-imap 115 | net-pop 116 | net-smtp 117 | marcel (1.0.4) 118 | mini_mime (1.1.5) 119 | minitest (5.25.5) 120 | net-imap (0.5.7) 121 | date 122 | net-protocol 123 | net-pop (0.1.2) 124 | net-protocol 125 | net-protocol (0.2.2) 126 | timeout 127 | net-smtp (0.5.1) 128 | net-protocol 129 | nio4r (2.7.4) 130 | nokogiri (1.18.8-arm64-darwin) 131 | racc (~> 1.4) 132 | parallel (1.27.0) 133 | parser (3.3.8.0) 134 | ast (~> 2.4.1) 135 | racc 136 | pp (0.6.2) 137 | prettyprint 138 | prettyprint (0.2.0) 139 | prism (1.4.0) 140 | psych (5.2.3) 141 | date 142 | stringio 143 | puma (6.6.0) 144 | nio4r (~> 2.0) 145 | racc (1.8.1) 146 | rack (3.1.13) 147 | rack-session (2.1.0) 148 | base64 (>= 0.1.0) 149 | rack (>= 3.0.0) 150 | rack-test (2.2.0) 151 | rack (>= 1.3) 152 | rackup (2.2.1) 153 | rack (>= 3) 154 | rails (8.0.2) 155 | actioncable (= 8.0.2) 156 | actionmailbox (= 8.0.2) 157 | actionmailer (= 8.0.2) 158 | actionpack (= 8.0.2) 159 | actiontext (= 8.0.2) 160 | actionview (= 8.0.2) 161 | activejob (= 8.0.2) 162 | activemodel (= 8.0.2) 163 | activerecord (= 8.0.2) 164 | activestorage (= 8.0.2) 165 | activesupport (= 8.0.2) 166 | bundler (>= 1.15.0) 167 | railties (= 8.0.2) 168 | rails-dom-testing (2.2.0) 169 | activesupport (>= 5.0.0) 170 | minitest 171 | nokogiri (>= 1.6) 172 | rails-html-sanitizer (1.6.2) 173 | loofah (~> 2.21) 174 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 175 | railties (8.0.2) 176 | actionpack (= 8.0.2) 177 | activesupport (= 8.0.2) 178 | irb (~> 1.13) 179 | rackup (>= 1.0.0) 180 | rake (>= 12.2) 181 | thor (~> 1.0, >= 1.2.2) 182 | zeitwerk (~> 2.6) 183 | rainbow (3.1.1) 184 | rake (13.2.1) 185 | rdoc (6.13.1) 186 | psych (>= 4.0.0) 187 | regexp_parser (2.10.0) 188 | reline (0.6.1) 189 | io-console (~> 0.5) 190 | rubocop (1.75.3) 191 | json (~> 2.3) 192 | language_server-protocol (~> 3.17.0.2) 193 | lint_roller (~> 1.1.0) 194 | parallel (~> 1.10) 195 | parser (>= 3.3.0.2) 196 | rainbow (>= 2.2.2, < 4.0) 197 | regexp_parser (>= 2.9.3, < 3.0) 198 | rubocop-ast (>= 1.44.0, < 2.0) 199 | ruby-progressbar (~> 1.7) 200 | unicode-display_width (>= 2.4.0, < 4.0) 201 | rubocop-ast (1.44.1) 202 | parser (>= 3.3.7.2) 203 | prism (~> 1.4) 204 | rubocop-performance (1.25.0) 205 | lint_roller (~> 1.1) 206 | rubocop (>= 1.75.0, < 2.0) 207 | rubocop-ast (>= 1.38.0, < 2.0) 208 | rubocop-rails (2.31.0) 209 | activesupport (>= 4.2.0) 210 | lint_roller (~> 1.1) 211 | rack (>= 1.1) 212 | rubocop (>= 1.75.0, < 2.0) 213 | rubocop-ast (>= 1.38.0, < 2.0) 214 | rubocop-rails-omakase (1.1.0) 215 | rubocop (>= 1.72) 216 | rubocop-performance (>= 1.24) 217 | rubocop-rails (>= 2.30) 218 | ruby-progressbar (1.13.0) 219 | securerandom (0.4.1) 220 | sprockets (4.2.2) 221 | concurrent-ruby (~> 1.0) 222 | logger 223 | rack (>= 2.2.4, < 4) 224 | sprockets-rails (3.5.2) 225 | actionpack (>= 6.1) 226 | activesupport (>= 6.1) 227 | sprockets (>= 3.0.0) 228 | sqlite3 (2.6.0-arm64-darwin) 229 | stringio (3.1.7) 230 | thor (1.3.2) 231 | timeout (0.4.3) 232 | tzinfo (2.0.6) 233 | concurrent-ruby (~> 1.0) 234 | unicode-display_width (3.1.4) 235 | unicode-emoji (~> 4.0, >= 4.0.4) 236 | unicode-emoji (4.0.4) 237 | uri (1.0.3) 238 | useragent (0.16.11) 239 | websocket-driver (0.7.7) 240 | base64 241 | websocket-extensions (>= 0.1.0) 242 | websocket-extensions (0.1.5) 243 | zeitwerk (2.7.2) 244 | 245 | PLATFORMS 246 | arm64-darwin 247 | 248 | DEPENDENCIES 249 | appraisal 250 | puma 251 | rails (~> 8.0.0) 252 | refer! 253 | rubocop-rails-omakase 254 | sprockets-rails 255 | sqlite3 256 | 257 | BUNDLED WITH 258 | 2.5.18 259 | -------------------------------------------------------------------------------- /gemfiles/rails_main.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "puma" 6 | gem "sqlite3" 7 | gem "sprockets-rails" 8 | gem "rubocop-rails-omakase", require: false 9 | gem "appraisal" 10 | gem "rails", github: "rails/rails" 11 | 12 | gemspec path: "../" 13 | -------------------------------------------------------------------------------- /gemfiles/rails_main.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/rails/rails.git 3 | revision: 99e27fa586af7db2b5334124a62eb3a464cdffd8 4 | specs: 5 | actioncable (8.1.0.alpha) 6 | actionpack (= 8.1.0.alpha) 7 | activesupport (= 8.1.0.alpha) 8 | nio4r (~> 2.0) 9 | websocket-driver (>= 0.6.1) 10 | zeitwerk (~> 2.6) 11 | actionmailbox (8.1.0.alpha) 12 | actionpack (= 8.1.0.alpha) 13 | activejob (= 8.1.0.alpha) 14 | activerecord (= 8.1.0.alpha) 15 | activestorage (= 8.1.0.alpha) 16 | activesupport (= 8.1.0.alpha) 17 | mail (>= 2.8.0) 18 | actionmailer (8.1.0.alpha) 19 | actionpack (= 8.1.0.alpha) 20 | actionview (= 8.1.0.alpha) 21 | activejob (= 8.1.0.alpha) 22 | activesupport (= 8.1.0.alpha) 23 | mail (>= 2.8.0) 24 | rails-dom-testing (~> 2.2) 25 | actionpack (8.1.0.alpha) 26 | actionview (= 8.1.0.alpha) 27 | activesupport (= 8.1.0.alpha) 28 | nokogiri (>= 1.8.5) 29 | rack (>= 2.2.4) 30 | rack-session (>= 1.0.1) 31 | rack-test (>= 0.6.3) 32 | rails-dom-testing (~> 2.2) 33 | rails-html-sanitizer (~> 1.6) 34 | useragent (~> 0.16) 35 | actiontext (8.1.0.alpha) 36 | actionpack (= 8.1.0.alpha) 37 | activerecord (= 8.1.0.alpha) 38 | activestorage (= 8.1.0.alpha) 39 | activesupport (= 8.1.0.alpha) 40 | globalid (>= 0.6.0) 41 | nokogiri (>= 1.8.5) 42 | actionview (8.1.0.alpha) 43 | activesupport (= 8.1.0.alpha) 44 | builder (~> 3.1) 45 | erubi (~> 1.11) 46 | rails-dom-testing (~> 2.2) 47 | rails-html-sanitizer (~> 1.6) 48 | activejob (8.1.0.alpha) 49 | activesupport (= 8.1.0.alpha) 50 | globalid (>= 0.3.6) 51 | activemodel (8.1.0.alpha) 52 | activesupport (= 8.1.0.alpha) 53 | activerecord (8.1.0.alpha) 54 | activemodel (= 8.1.0.alpha) 55 | activesupport (= 8.1.0.alpha) 56 | timeout (>= 0.4.0) 57 | activestorage (8.1.0.alpha) 58 | actionpack (= 8.1.0.alpha) 59 | activejob (= 8.1.0.alpha) 60 | activerecord (= 8.1.0.alpha) 61 | activesupport (= 8.1.0.alpha) 62 | marcel (~> 1.0) 63 | activesupport (8.1.0.alpha) 64 | base64 65 | benchmark (>= 0.3) 66 | bigdecimal 67 | concurrent-ruby (~> 1.0, >= 1.3.1) 68 | connection_pool (>= 2.2.5) 69 | drb 70 | i18n (>= 1.6, < 2) 71 | logger (>= 1.4.2) 72 | minitest (>= 5.1) 73 | securerandom (>= 0.3) 74 | tzinfo (~> 2.0, >= 2.0.5) 75 | uri (>= 0.13.1) 76 | rails (8.1.0.alpha) 77 | actioncable (= 8.1.0.alpha) 78 | actionmailbox (= 8.1.0.alpha) 79 | actionmailer (= 8.1.0.alpha) 80 | actionpack (= 8.1.0.alpha) 81 | actiontext (= 8.1.0.alpha) 82 | actionview (= 8.1.0.alpha) 83 | activejob (= 8.1.0.alpha) 84 | activemodel (= 8.1.0.alpha) 85 | activerecord (= 8.1.0.alpha) 86 | activestorage (= 8.1.0.alpha) 87 | activesupport (= 8.1.0.alpha) 88 | bundler (>= 1.15.0) 89 | railties (= 8.1.0.alpha) 90 | railties (8.1.0.alpha) 91 | actionpack (= 8.1.0.alpha) 92 | activesupport (= 8.1.0.alpha) 93 | irb (~> 1.13) 94 | rackup (>= 1.0.0) 95 | rake (>= 12.2) 96 | thor (~> 1.0, >= 1.2.2) 97 | zeitwerk (~> 2.6) 98 | 99 | PATH 100 | remote: .. 101 | specs: 102 | refer (1.0.3) 103 | rails (>= 7.1.0) 104 | 105 | GEM 106 | remote: https://rubygems.org/ 107 | specs: 108 | appraisal (2.5.0) 109 | bundler 110 | rake 111 | thor (>= 0.14.0) 112 | ast (2.4.3) 113 | base64 (0.2.0) 114 | benchmark (0.4.0) 115 | bigdecimal (3.1.9) 116 | builder (3.3.0) 117 | concurrent-ruby (1.3.5) 118 | connection_pool (2.5.2) 119 | crass (1.0.6) 120 | date (3.4.1) 121 | drb (2.2.1) 122 | erubi (1.13.1) 123 | globalid (1.2.1) 124 | activesupport (>= 6.1) 125 | i18n (1.14.7) 126 | concurrent-ruby (~> 1.0) 127 | io-console (0.8.0) 128 | irb (1.15.2) 129 | pp (>= 0.6.0) 130 | rdoc (>= 4.0.0) 131 | reline (>= 0.4.2) 132 | json (2.11.3) 133 | language_server-protocol (3.17.0.4) 134 | lint_roller (1.1.0) 135 | logger (1.7.0) 136 | loofah (2.24.0) 137 | crass (~> 1.0.2) 138 | nokogiri (>= 1.12.0) 139 | mail (2.8.1) 140 | mini_mime (>= 0.1.1) 141 | net-imap 142 | net-pop 143 | net-smtp 144 | marcel (1.0.4) 145 | mini_mime (1.1.5) 146 | mini_portile2 (2.8.8) 147 | minitest (5.25.5) 148 | net-imap (0.5.7) 149 | date 150 | net-protocol 151 | net-pop (0.1.2) 152 | net-protocol 153 | net-protocol (0.2.2) 154 | timeout 155 | net-smtp (0.5.1) 156 | net-protocol 157 | nio4r (2.7.4) 158 | nokogiri (1.18.8) 159 | mini_portile2 (~> 2.8.2) 160 | racc (~> 1.4) 161 | nokogiri (1.18.8-aarch64-linux-gnu) 162 | racc (~> 1.4) 163 | nokogiri (1.18.8-aarch64-linux-musl) 164 | racc (~> 1.4) 165 | nokogiri (1.18.8-arm-linux-gnu) 166 | racc (~> 1.4) 167 | nokogiri (1.18.8-arm-linux-musl) 168 | racc (~> 1.4) 169 | nokogiri (1.18.8-arm64-darwin) 170 | racc (~> 1.4) 171 | nokogiri (1.18.8-x86_64-darwin) 172 | racc (~> 1.4) 173 | nokogiri (1.18.8-x86_64-linux-gnu) 174 | racc (~> 1.4) 175 | nokogiri (1.18.8-x86_64-linux-musl) 176 | racc (~> 1.4) 177 | parallel (1.27.0) 178 | parser (3.3.8.0) 179 | ast (~> 2.4.1) 180 | racc 181 | pp (0.6.2) 182 | prettyprint 183 | prettyprint (0.2.0) 184 | prism (1.4.0) 185 | psych (5.2.3) 186 | date 187 | stringio 188 | puma (6.6.0) 189 | nio4r (~> 2.0) 190 | racc (1.8.1) 191 | rack (3.1.13) 192 | rack-session (2.1.0) 193 | base64 (>= 0.1.0) 194 | rack (>= 3.0.0) 195 | rack-test (2.2.0) 196 | rack (>= 1.3) 197 | rackup (2.2.1) 198 | rack (>= 3) 199 | rails-dom-testing (2.2.0) 200 | activesupport (>= 5.0.0) 201 | minitest 202 | nokogiri (>= 1.6) 203 | rails-html-sanitizer (1.6.2) 204 | loofah (~> 2.21) 205 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 206 | rainbow (3.1.1) 207 | rake (13.2.1) 208 | rdoc (6.13.1) 209 | psych (>= 4.0.0) 210 | regexp_parser (2.10.0) 211 | reline (0.6.1) 212 | io-console (~> 0.5) 213 | rubocop (1.75.3) 214 | json (~> 2.3) 215 | language_server-protocol (~> 3.17.0.2) 216 | lint_roller (~> 1.1.0) 217 | parallel (~> 1.10) 218 | parser (>= 3.3.0.2) 219 | rainbow (>= 2.2.2, < 4.0) 220 | regexp_parser (>= 2.9.3, < 3.0) 221 | rubocop-ast (>= 1.44.0, < 2.0) 222 | ruby-progressbar (~> 1.7) 223 | unicode-display_width (>= 2.4.0, < 4.0) 224 | rubocop-ast (1.44.1) 225 | parser (>= 3.3.7.2) 226 | prism (~> 1.4) 227 | rubocop-performance (1.25.0) 228 | lint_roller (~> 1.1) 229 | rubocop (>= 1.75.0, < 2.0) 230 | rubocop-ast (>= 1.38.0, < 2.0) 231 | rubocop-rails (2.31.0) 232 | activesupport (>= 4.2.0) 233 | lint_roller (~> 1.1) 234 | rack (>= 1.1) 235 | rubocop (>= 1.75.0, < 2.0) 236 | rubocop-ast (>= 1.38.0, < 2.0) 237 | rubocop-rails-omakase (1.1.0) 238 | rubocop (>= 1.72) 239 | rubocop-performance (>= 1.24) 240 | rubocop-rails (>= 2.30) 241 | ruby-progressbar (1.13.0) 242 | securerandom (0.4.1) 243 | sprockets (4.2.2) 244 | concurrent-ruby (~> 1.0) 245 | logger 246 | rack (>= 2.2.4, < 4) 247 | sprockets-rails (3.5.2) 248 | actionpack (>= 6.1) 249 | activesupport (>= 6.1) 250 | sprockets (>= 3.0.0) 251 | sqlite3 (2.6.0-aarch64-linux-gnu) 252 | sqlite3 (2.6.0-aarch64-linux-musl) 253 | sqlite3 (2.6.0-arm-linux-gnu) 254 | sqlite3 (2.6.0-arm-linux-musl) 255 | sqlite3 (2.6.0-arm64-darwin) 256 | sqlite3 (2.6.0-x86-linux-gnu) 257 | sqlite3 (2.6.0-x86-linux-musl) 258 | sqlite3 (2.6.0-x86_64-darwin) 259 | sqlite3 (2.6.0-x86_64-linux-gnu) 260 | sqlite3 (2.6.0-x86_64-linux-musl) 261 | stringio (3.1.7) 262 | thor (1.3.2) 263 | timeout (0.4.3) 264 | tzinfo (2.0.6) 265 | concurrent-ruby (~> 1.0) 266 | unicode-display_width (3.1.4) 267 | unicode-emoji (~> 4.0, >= 4.0.4) 268 | unicode-emoji (4.0.4) 269 | uri (1.0.3) 270 | useragent (0.16.11) 271 | websocket-driver (0.7.7) 272 | base64 273 | websocket-extensions (>= 0.1.0) 274 | websocket-extensions (0.1.5) 275 | zeitwerk (2.7.2) 276 | 277 | PLATFORMS 278 | aarch64-linux 279 | aarch64-linux-gnu 280 | aarch64-linux-musl 281 | arm-linux 282 | arm-linux-gnu 283 | arm-linux-musl 284 | arm64-darwin 285 | x86-linux 286 | x86-linux-gnu 287 | x86-linux-musl 288 | x86_64-darwin 289 | x86_64-linux 290 | x86_64-linux-gnu 291 | x86_64-linux-musl 292 | 293 | DEPENDENCIES 294 | appraisal 295 | puma 296 | rails! 297 | refer! 298 | rubocop-rails-omakase 299 | sprockets-rails 300 | sqlite3 301 | 302 | BUNDLED WITH 303 | 2.5.18 304 | -------------------------------------------------------------------------------- /lib/generators/refer/install/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Adds Refer to ApplicationController to set referral cookies 3 | 4 | Example: 5 | bin/rails generate refer:install 6 | 7 | This will add `sets_refer_cookie` to the ApplicationController class. 8 | 9 | -------------------------------------------------------------------------------- /lib/generators/refer/install/install_generator.rb: -------------------------------------------------------------------------------- 1 | class Refer::InstallGenerator < Rails::Generators::Base 2 | source_root File.expand_path("templates", __dir__) 3 | 4 | def add_refer 5 | inject_into_class File.join("app", "controllers", "application_controller.rb"), "ApplicationController", " set_referral_cookie\n" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/generators/refer/model/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Adds Refer associations to a model 3 | 4 | Example: 5 | bin/rails generate refer:model User 6 | 7 | This will add `has_referrals` to the User class. 8 | -------------------------------------------------------------------------------- /lib/generators/refer/model/model_generator.rb: -------------------------------------------------------------------------------- 1 | class Refer::ModelGenerator < Rails::Generators::NamedBase 2 | source_root File.expand_path("templates", __dir__) 3 | 4 | def migrations 5 | rails_command "refer:install:migrations" 6 | end 7 | 8 | def add_refer 9 | inject_into_class File.join("app", "models", "#{file_path}.rb"), class_name, " has_referrals\n" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/refer.rb: -------------------------------------------------------------------------------- 1 | require "refer/version" 2 | require "refer/engine" 3 | require "securerandom" 4 | 5 | module Refer 6 | include ActiveSupport::Configurable 7 | 8 | autoload :Controller, "refer/controller" 9 | autoload :HasReferrals, "refer/has_referrals" 10 | autoload :Model, "refer/model" 11 | 12 | config_accessor :code_generator, default: ->(referrer) { SecureRandom.alphanumeric(8) } 13 | config_accessor :cookie_length, default: 30.days 14 | config_accessor :cookie_name, default: :refer_code 15 | config_accessor :param_name, default: :ref 16 | config_accessor :overwrite_cookie, default: true 17 | config_accessor :track_visits, default: true 18 | config_accessor :mask_ips, default: true 19 | config_accessor :referral_completed 20 | 21 | class Error < StandardError; end 22 | class AlreadyReferred < Error; end 23 | 24 | def self.referred?(referee) 25 | Referral.where(referee: referee).exists? 26 | end 27 | 28 | def self.refer(code:, referee:) 29 | return if referred?(referee) 30 | ReferralCode.find_by(code: code)&.referrals&.create(referee: referee) 31 | end 32 | 33 | def self.refer!(code:, referee:) 34 | raise AlreadyReferred, "#{referee} has already been referred" if referred?(referee) 35 | ReferralCode.find_by!(code: code).referrals.create!(referee: referee) 36 | end 37 | 38 | def self.cookie(code) 39 | { 40 | value: code, 41 | expires: Refer.cookie_length.from_now 42 | } 43 | end 44 | 45 | # From Ahoy gem: https://github.com/ankane/ahoy/blob/v5.1.0/lib/ahoy.rb#L133-L142 46 | def self.mask_ip(ip) 47 | return ip unless mask_ips 48 | 49 | addr = IPAddr.new(ip) 50 | if addr.ipv4? 51 | # set last octet to 0 52 | addr.mask(24).to_s 53 | else 54 | # set last 80 bits to zeros 55 | addr.mask(48).to_s 56 | end 57 | end 58 | end 59 | 60 | ActiveSupport.run_load_hooks(:refer, Refer) 61 | -------------------------------------------------------------------------------- /lib/refer/controller.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | module Controller 3 | extend ActiveSupport::Concern 4 | 5 | class_methods do 6 | def set_referral_cookie(param_name: Refer.param_name, cookie_name: Refer.cookie_name, **options) 7 | before_action -> { set_refer_cookie(param_name: param_name, cookie_name: cookie_name) }, **options 8 | end 9 | end 10 | 11 | def refer(referee, cookie_name: Refer.cookie_name) 12 | Refer.refer(code: cookies[cookie_name], referee: referee) 13 | end 14 | 15 | private 16 | 17 | def set_refer_cookie(param_name: Refer.param_name, cookie_name: Refer.cookie_name, code: nil, track_visit: Refer.track_visits) 18 | code ||= params[param_name] 19 | return if code.blank? 20 | 21 | cookies[cookie_name] = Refer.cookie(code) if Refer.overwrite_cookie || cookies[cookie_name].blank? 22 | ReferralCode.find_by(code: code)&.track_visit(request) if track_visit 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/refer/engine.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | class Engine < ::Rails::Engine 3 | isolate_namespace Refer 4 | 5 | initializer "refer.hooks" do 6 | ActiveSupport.on_load(:active_record) do 7 | include Refer::HasReferrals 8 | end 9 | 10 | ActiveSupport.on_load(:action_controller) do 11 | include Refer::Controller 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/refer/has_referrals.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | module HasReferrals 3 | extend ActiveSupport::Concern 4 | 5 | class_methods do 6 | def has_referrals 7 | include Refer::Model 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/refer/model.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | module Model 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | has_many :referral_codes, as: :referrer, class_name: "Refer::ReferralCode", dependent: :destroy 7 | has_many :referrals, as: :referrer, class_name: "Refer::Referral", dependent: :destroy 8 | has_one :referral, as: :referee, class_name: "Refer::Referral", dependent: :destroy 9 | delegate :referrer, to: :referral, allow_nil: true 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/refer/version.rb: -------------------------------------------------------------------------------- 1 | module Refer 2 | VERSION = "1.0.3" 3 | end 4 | -------------------------------------------------------------------------------- /lib/tasks/refer_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :refer do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /refer.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/refer/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "refer" 5 | spec.version = Refer::VERSION 6 | spec.authors = [ "Chris Oliver" ] 7 | spec.email = [ "excid3@gmail.com" ] 8 | spec.homepage = "https://github.com/excid3/refer" 9 | spec.summary = "Referral codes & affiliate links for Ruby on Rails apps" 10 | spec.description = "Referral codes & affiliate links for Ruby on Rails apps" 11 | spec.license = "MIT" 12 | 13 | spec.metadata["homepage_uri"] = spec.homepage 14 | spec.metadata["source_code_uri"] = spec.homepage 15 | spec.metadata["changelog_uri"] = spec.homepage + "/blob/main/CHANGELOG.md" 16 | 17 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 18 | Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 19 | end 20 | 21 | spec.add_dependency "rails", ">= 7.1.0" 22 | end 23 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /test/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/controllers/referrals_controller.rb: -------------------------------------------------------------------------------- 1 | class ReferralsController < ApplicationController 2 | set_referral_cookie 3 | before_action :set_user, only: [ :create ] 4 | 5 | def show 6 | head :ok 7 | end 8 | 9 | def create 10 | refer @user 11 | head :ok 12 | end 13 | 14 | private 15 | 16 | def set_user 17 | @user = User.find(params[:user_id]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | has_referrals 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <title><%= content_for(:title) || "Dummy" %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | 12 | 13 | 14 | 15 | 16 | <%= stylesheet_link_tag "application" %> 17 | 18 | 19 | 20 | <%= yield %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /test/dummy/app/views/pwa/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dummy", 3 | "icons": [ 4 | { 5 | "src": "/icon.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | }, 9 | { 10 | "src": "/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "scope": "/", 19 | "description": "Dummy.", 20 | "theme_color": "red", 21 | "background_color": "red" 22 | } 23 | -------------------------------------------------------------------------------- /test/dummy/app/views/pwa/service-worker.js: -------------------------------------------------------------------------------- 1 | // Add a service worker for processing Web Push notifications: 2 | // 3 | // self.addEventListener("push", async (event) => { 4 | // const { title, options } = await event.data.json() 5 | // event.waitUntil(self.registration.showNotification(title, options)) 6 | // }) 7 | // 8 | // self.addEventListener("notificationclick", function(event) { 9 | // event.notification.close() 10 | // event.waitUntil( 11 | // clients.matchAll({ type: "window" }).then((clientList) => { 12 | // for (let i = 0; i < clientList.length; i++) { 13 | // let client = clientList[i] 14 | // let clientPath = (new URL(client.url)).pathname 15 | // 16 | // if (clientPath == event.notification.data.path && "focus" in client) { 17 | // return client.focus() 18 | // } 19 | // } 20 | // 21 | // if (clients.openWindow) { 22 | // return clients.openWindow(event.notification.data.path) 23 | // } 24 | // }) 25 | // ) 26 | // }) 27 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | APP_NAME = "dummy" 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | 34 | # puts "\n== Configuring puma-dev ==" 35 | # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" 36 | # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" 37 | end 38 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Dummy 10 | class Application < Rails::Application 11 | config.load_defaults Rails::VERSION::STRING.to_f 12 | 13 | # For compatibility with applications that use this config 14 | config.action_controller.include_all_helpers = false 15 | 16 | # Please, add to the `ignore` list any other `lib` subdirectories that do 17 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 18 | # Common ones are `templates`, `generators`, or `middleware`, for example. 19 | config.autoload_lib(ignore: %w[assets tasks]) 20 | 21 | # Configuration for the application, engines, and railties goes here. 22 | # 23 | # These settings can be overridden in specific environments using the files 24 | # in config/environments, which are processed later. 25 | # 26 | # config.time_zone = "Central Time (US & Canada)" 27 | # config.eager_load_paths << Rails.root.join("extras") 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) 3 | 4 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 5 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) 6 | -------------------------------------------------------------------------------- /test/dummy/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dummy_production 11 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | 24 | # SQLite3 write its data on the local filesystem, as such it requires 25 | # persistent disks. If you are deploying to a managed service, you should 26 | # make sure it provides disk persistence, as many don't. 27 | # 28 | # Similarly, if you deploy your application as a Docker container, you must 29 | # ensure the database is located in a persisted volume. 30 | production: 31 | <<: *default 32 | # database: path/to/persistent/storage/production.sqlite3 33 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.enable_reloading = true 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing. 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join("tmp/caching-dev.txt").exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } 28 | else 29 | config.action_controller.perform_caching = false 30 | 31 | config.cache_store = :null_store 32 | end 33 | 34 | # Store uploaded files on the local file system (see config/storage.yml for options). 35 | config.active_storage.service = :local 36 | 37 | # Don't care if the mailer can't send. 38 | config.action_mailer.raise_delivery_errors = false 39 | 40 | config.action_mailer.perform_caching = false 41 | 42 | config.action_mailer.default_url_options = { host: "localhost", port: 3000 } 43 | 44 | # Print deprecation notices to the Rails logger. 45 | config.active_support.deprecation = :log 46 | 47 | # Raise exceptions for disallowed deprecations. 48 | config.active_support.disallowed_deprecation = :raise 49 | 50 | # Tell Active Support which deprecation messages to disallow. 51 | config.active_support.disallowed_deprecation_warnings = [] 52 | 53 | # Raise an error on page load if there are pending migrations. 54 | config.active_record.migration_error = :page_load 55 | 56 | # Highlight code that triggered database queries in logs. 57 | config.active_record.verbose_query_logs = true 58 | 59 | # Highlight code that enqueued background job in logs. 60 | config.active_job.verbose_enqueue_logs = true 61 | 62 | # Suppress logger output for asset requests. 63 | config.assets.quiet = true 64 | 65 | # Raises error for missing translations. 66 | # config.i18n.raise_on_missing_translations = true 67 | 68 | # Annotate rendered view with file names. 69 | config.action_view.annotate_rendered_view_with_filenames = true 70 | 71 | # Uncomment if you wish to allow Action Cable access from any origin. 72 | # config.action_cable.disable_request_forgery_protection = true 73 | 74 | # Raise error when a before_action's only/except options reference missing actions. 75 | config.action_controller.raise_on_missing_callback_actions = true 76 | end 77 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.enable_reloading = false 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment 20 | # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. 24 | # config.public_file_server.enabled = false 25 | 26 | # Compress CSS using a preprocessor. 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fall back to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 33 | # config.asset_host = "http://assets.example.com" 34 | 35 | # Specifies the header that your server uses for sending files. 36 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 37 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 38 | 39 | # Store uploaded files on the local file system (see config/storage.yml for options). 40 | config.active_storage.service = :local 41 | 42 | # Mount Action Cable outside main process or domain. 43 | # config.action_cable.mount_path = nil 44 | # config.action_cable.url = "wss://example.com/cable" 45 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 46 | 47 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 48 | # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. 49 | # config.assume_ssl = true 50 | 51 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 52 | config.force_ssl = true 53 | # Skip http-to-https redirect for the default health check endpoint. 54 | # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } 55 | 56 | # Log to STDOUT by default 57 | config.logger = ActiveSupport::Logger.new(STDOUT) 58 | .tap { |logger| logger.formatter = ::Logger::Formatter.new } 59 | .then { |logger| ActiveSupport::TaggedLogging.new(logger) } 60 | 61 | # Prepend all log lines with the following tags. 62 | config.log_tags = [ :request_id ] 63 | 64 | # "info" includes generic and useful information about system operation, but avoids logging too much 65 | # information to avoid inadvertent exposure of personally identifiable information (PII). If you 66 | # want to log everything, set the level to "debug". 67 | config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") 68 | 69 | # Use a different cache store in production. 70 | # config.cache_store = :mem_cache_store 71 | 72 | # Use a real queuing backend for Active Job (and separate queues per environment). 73 | # config.active_job.queue_adapter = :resque 74 | # config.active_job.queue_name_prefix = "dummy_production" 75 | 76 | config.action_mailer.perform_caching = false 77 | 78 | # Ignore bad email addresses and do not raise email delivery errors. 79 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 80 | # config.action_mailer.raise_delivery_errors = false 81 | 82 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 83 | # the I18n.default_locale when a translation cannot be found). 84 | config.i18n.fallbacks = true 85 | 86 | # Don't log any deprecations. 87 | config.active_support.report_deprecations = false 88 | 89 | # Do not dump schema after migrations. 90 | config.active_record.dump_schema_after_migration = false 91 | 92 | # Enable DNS rebinding protection and other `Host` header attacks. 93 | # config.hosts = [ 94 | # "example.com", # Allow requests from example.com 95 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 96 | # ] 97 | # Skip DNS rebinding protection for the default health check endpoint. 98 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 99 | end 100 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV["CI"].present? 19 | 20 | # Configure public file server for tests with Cache-Control for performance. 21 | config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | config.cache_store = :null_store 27 | 28 | # Render exception templates for rescuable exceptions and raise for other exceptions. 29 | config.action_dispatch.show_exceptions = :rescuable 30 | 31 | # Disable request forgery protection in test environment. 32 | config.action_controller.allow_forgery_protection = false 33 | 34 | # Store uploaded files on the local file system in a temporary directory. 35 | config.active_storage.service = :test 36 | 37 | config.action_mailer.perform_caching = false 38 | 39 | # Tell Action Mailer not to deliver emails to the real world. 40 | # The :test delivery method accumulates sent emails in the 41 | # ActionMailer::Base.deliveries array. 42 | config.action_mailer.delivery_method = :test 43 | 44 | # Unlike controllers, the mailer instance doesn't have any context about the 45 | # incoming request so you'll need to provide the :host parameter yourself. 46 | config.action_mailer.default_url_options = { host: "www.example.com" } 47 | 48 | # Print deprecation notices to the stderr. 49 | config.active_support.deprecation = :stderr 50 | 51 | # Raise exceptions for disallowed deprecations. 52 | config.active_support.disallowed_deprecation = :raise 53 | 54 | # Tell Active Support which deprecation messages to disallow. 55 | config.active_support.disallowed_deprecation_warnings = [] 56 | 57 | # Raises error for missing translations. 58 | # config.i18n.raise_on_missing_translations = true 59 | 60 | # Annotate rendered view with file names. 61 | # config.action_view.annotate_rendered_view_with_filenames = true 62 | 63 | # Raise error when a before_action's only/except options reference missing actions. 64 | config.action_controller.raise_on_missing_callback_actions = true 65 | end 66 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /test/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | 5 | # Puma starts a configurable number of processes (workers) and each process 6 | # serves each request in a thread from an internal thread pool. 7 | # 8 | # The ideal number of threads per worker depends both on how much time the 9 | # application spends waiting for IO operations and on how much you wish to 10 | # to prioritize throughput over latency. 11 | # 12 | # As a rule of thumb, increasing the number of threads will increase how much 13 | # traffic a given process can handle (throughput), but due to CRuby's 14 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 15 | # response time (latency) of the application. 16 | # 17 | # The default is set to 3 threads as it's deemed a decent compromise between 18 | # throughput and latency for the average Rails application. 19 | # 20 | # Any libraries that use a connection pool or another resource pool should 21 | # be configured to provide at least as many connections as the number of 22 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 23 | threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) 24 | threads threads_count, threads_count 25 | 26 | # Specifies the `environment` that Puma will run in. 27 | rails_env = ENV.fetch("RAILS_ENV", "development") 28 | environment rails_env 29 | 30 | case rails_env 31 | when "production" 32 | # If you are running more than 1 thread per process, the workers count 33 | # should be equal to the number of processors (CPU cores) in production. 34 | # 35 | # Automatically detect the number of available processors in production. 36 | require "concurrent-ruby" 37 | workers_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.available_processor_count }) 38 | workers workers_count if workers_count > 1 39 | 40 | preload_app! 41 | when "development" 42 | # Specifies a very generous `worker_timeout` so that the worker 43 | # isn't killed by Puma when suspended by a debugger. 44 | worker_timeout 3600 45 | end 46 | 47 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 48 | port ENV.fetch("PORT", 3000) 49 | 50 | # Allow puma to be restarted by `bin/rails restart` command. 51 | plugin :tmp_restart 52 | 53 | # Only use a pidfile when requested 54 | pidfile ENV["PIDFILE"] if ENV["PIDFILE"] 55 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 3 | post :refer, to: "referrals#create" 4 | 5 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 6 | # Can be used by load balancers and uptime monitors to verify that the app is live. 7 | get "up" => "rails/health#show", as: :rails_health_check 8 | 9 | # Render dynamic PWA files from app/views/pwa/* 10 | get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker 11 | get "manifest" => "rails/pwa#manifest", as: :pwa_manifest 12 | 13 | # Defines the root path route ("/") 14 | root to: "referrals#show" 15 | end 16 | -------------------------------------------------------------------------------- /test/dummy/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20240611180748_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[7.2] 2 | def change 3 | create_table :users do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.2].define(version: 2024_07_01_172643) do 14 | create_table "refer_referral_codes", force: :cascade do |t| 15 | t.string "referrer_type", null: false 16 | t.integer "referrer_id", null: false 17 | t.string "code", null: false 18 | t.datetime "created_at", null: false 19 | t.datetime "updated_at", null: false 20 | t.integer "referrals_count", default: 0 21 | t.integer "visits_count", default: 0 22 | t.index [ "code" ], name: "index_refer_referral_codes_on_code", unique: true 23 | t.index [ "referrer_type", "referrer_id" ], name: "index_refer_referral_codes_on_referrer" 24 | end 25 | 26 | create_table "refer_referrals", force: :cascade do |t| 27 | t.string "referrer_type", null: false 28 | t.integer "referrer_id", null: false 29 | t.string "referee_type", null: false 30 | t.integer "referee_id", null: false 31 | t.integer "referral_code_id" 32 | t.datetime "created_at", null: false 33 | t.datetime "updated_at", null: false 34 | t.datetime "completed_at" 35 | t.index [ "referee_type", "referee_id" ], name: "index_refer_referrals_on_referee" 36 | t.index [ "referral_code_id" ], name: "index_refer_referrals_on_referral_code_id" 37 | t.index [ "referrer_type", "referrer_id" ], name: "index_refer_referrals_on_referrer" 38 | end 39 | 40 | create_table "refer_visits", force: :cascade do |t| 41 | t.integer "referral_code_id", null: false 42 | t.string "ip" 43 | t.text "user_agent" 44 | t.text "referrer" 45 | t.string "referring_domain" 46 | t.datetime "created_at", null: false 47 | t.datetime "updated_at", null: false 48 | t.index [ "referral_code_id" ], name: "index_refer_visits_on_referral_code_id" 49 | end 50 | 51 | create_table "users", force: :cascade do |t| 52 | t.string "name" 53 | t.datetime "created_at", null: false 54 | t.datetime "updated_at", null: false 55 | end 56 | 57 | add_foreign_key "refer_visits", "refer_referral_codes", column: "referral_code_id" 58 | end 59 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/dummy/lib/assets/.keep -------------------------------------------------------------------------------- /test/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/dummy/log/.keep -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/406-unsupported-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your browser is not supported (406) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

Your browser is not supported.

62 |

Please upgrade your browser to continue.

63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/dummy/public/icon.png -------------------------------------------------------------------------------- /test/dummy/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/refer/referral_codes.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | referrer: one (User) 5 | code: chris 6 | 7 | two: 8 | referrer: two (User) 9 | code: bob 10 | -------------------------------------------------------------------------------- /test/fixtures/refer/referrals.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | referrer: one (User) 5 | referee: two (User) 6 | referral_code: one 7 | -------------------------------------------------------------------------------- /test/fixtures/refer/visits.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | referral_code: one 5 | ip: MyString 6 | user_agent: MyText 7 | referrer: MyText 8 | referring_domain: MyString 9 | 10 | two: 11 | referral_code: two 12 | ip: MyString 13 | user_agent: MyText 14 | referrer: MyText 15 | referring_domain: MyString 16 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: Chris 5 | 6 | two: 7 | name: Bob 8 | 9 | new: 10 | name: David 11 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/integration/.keep -------------------------------------------------------------------------------- /test/integration/referral_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ReferralIntegrationTest < ActionDispatch::IntegrationTest 4 | test "referral is created" do 5 | assert_difference "Refer::Referral.count" do 6 | post refer_path(ref: refer_referral_codes(:one), user_id: users(:new).id) 7 | end 8 | end 9 | 10 | test "doesn't set referral cookie if param missing" do 11 | get root_path(other: "example") 12 | assert_nil cookies[Refer.cookie_name] 13 | end 14 | 15 | test "sets referral cookie" do 16 | referral_code = refer_referral_codes(:one) 17 | get root_path(ref: referral_code) 18 | assert_equal referral_code.to_param, cookies[Refer.cookie_name] 19 | end 20 | 21 | test "tracks visits" do 22 | referral_code = refer_referral_codes(:one) 23 | assert_difference "referral_code.reload.visits_count" do 24 | get root_path(ref: referral_code) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/excid3/refer/a64a19487a2b1da31e6875bc19eee0be5e42703d/test/models/.keep -------------------------------------------------------------------------------- /test/models/refer/referral_code_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "minitest/mock" 3 | 4 | 5 | class Refer::ReferralCodeTest < ActiveSupport::TestCase 6 | test "deleting referral code doesn't delete referral" do 7 | referral = refer_referrals(:one) 8 | assert_not_nil referral.referral_code 9 | assert_no_difference "Refer::Referral.count" do 10 | referral.referral_code.destroy 11 | end 12 | referral.reload 13 | assert_nil referral.referral_code 14 | end 15 | 16 | test "referral codes are dependent destroyed" do 17 | assert_difference "Refer::ReferralCode.count", -1 do 18 | users(:one).destroy 19 | end 20 | end 21 | 22 | test "generates referral codes automatically" do 23 | Refer.stub :code_generator, -> { ->(referrer) { SecureRandom.alphanumeric(8) } } do 24 | assert_not_nil users(:one).referral_codes.create!.code 25 | end 26 | end 27 | 28 | test "does not generate referral code automatically if empty generator config" do 29 | Refer.stub :code_generator, nil do 30 | assert_nil users(:one).referral_codes.create.code 31 | end 32 | end 33 | 34 | test "does not generate referral code automatically if using a custom code" do 35 | Refer.stub :code_generator, ->(referrer) { SecureRandom.alphanumeric(8) } do 36 | custom_referral = users(:one).referral_codes.create(code: "custom") 37 | assert_equal custom_referral.code, "custom" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/models/refer/referral_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class Refer::ReferralTest < ActiveSupport::TestCase 4 | test "referrals are dependent destroyed" do 5 | assert_difference "Refer::Referral.count", -1 do 6 | users(:one).destroy 7 | end 8 | end 9 | 10 | test "can be associated with a referral code" do 11 | assert_equal refer_referral_codes(:one), refer_referrals(:one).referral_code 12 | end 13 | 14 | test "can be completed" do 15 | referral = refer_referrals(:one) 16 | assert_nil referral.completed_at 17 | 18 | travel_to Time.current do 19 | referral.complete! 20 | assert_equal Time.current, referral.completed_at 21 | end 22 | end 23 | 24 | test "complete with custom attributes" do 25 | referral = refer_referrals(:one) 26 | assert_nil referral.completed_at 27 | 28 | travel_to Time.current do 29 | referral.complete!(completed_at: 1.hour.ago) 30 | assert_equal 1.hour.ago, referral.completed_at 31 | end 32 | end 33 | 34 | test "complete! doesn't override previous completion" do 35 | referral = refer_referrals(:one) 36 | assert_nil referral.completed_at 37 | 38 | referral.complete! 39 | 40 | travel_to Time.current + 1.hour do 41 | assert_no_difference "referral.completed_at" do 42 | referral.complete! 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/models/refer/visit_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Refer 4 | class VisitTest < ActiveSupport::TestCase 5 | test "masks ips" do 6 | assert_equal "127.0.0.0", Refer::Visit.new(ip: "127.0.0.1").ip 7 | end 8 | 9 | test "referring_domain" do 10 | request = ActiveSupport::OrderedOptions.new.merge(referrer: "https://gorails.com/episodes/1") 11 | assert_equal "gorails.com", Refer::Visit.from_request(request).referring_domain 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/refer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ReferTest < ActiveSupport::TestCase 4 | test "it has a version number" do 5 | assert Refer::VERSION 6 | end 7 | 8 | test "refer" do 9 | assert_difference "Refer::Referral.count" do 10 | Refer.refer(code: refer_referral_codes(:one).code, referee: users(:new)) 11 | end 12 | end 13 | 14 | test "refer invalid code" do 15 | assert_raises ActiveRecord::RecordNotFound do 16 | Refer.refer!(code: "invalid", referee: users(:new)) 17 | end 18 | end 19 | 20 | test "refer self" do 21 | assert_no_difference "Refer::Referral.count" do 22 | Refer.refer(code: refer_referral_codes(:one).code, referee: users(:one)) 23 | end 24 | 25 | assert_raises ActiveRecord::RecordInvalid, "Validation failed: Self-referrals are not allowed" do 26 | Refer.refer!(code: refer_referral_codes(:one).code, referee: users(:one)) 27 | end 28 | end 29 | 30 | test "refer already referred" do 31 | assert_no_difference "Refer::Referral.count" do 32 | Refer.refer(code: refer_referral_codes(:one).code, referee: users(:two)) 33 | end 34 | 35 | assert_raises Refer::AlreadyReferred do 36 | Refer.refer!(code: refer_referral_codes(:one).code, referee: users(:two)) 37 | end 38 | end 39 | 40 | test "referred?" do 41 | assert Refer.referred?(users(:two)) 42 | assert_not Refer.referred?(users(:new)) 43 | end 44 | 45 | test "referral_codes" do 46 | assert_includes users(:one).referral_codes, refer_referral_codes(:one) 47 | end 48 | 49 | test "referrals" do 50 | assert_includes users(:one).referrals, refer_referrals(:one) 51 | end 52 | 53 | test "referral" do 54 | assert_equal refer_referrals(:one), users(:two).referral 55 | end 56 | 57 | test "referrer" do 58 | assert_equal users(:one), users(:two).referrer 59 | end 60 | 61 | test "referrer when nil" do 62 | assert_nil users(:new).referrer 63 | end 64 | 65 | test "referral_completed callback" do 66 | old_callback = Refer.referral_completed 67 | referral = refer_referrals(:one) 68 | assert_not referral.completed_at? 69 | 70 | completed = nil 71 | Refer.referral_completed = ->(referral) { 72 | completed = referral 73 | } 74 | 75 | # Called the first time a referral is completed 76 | referral.complete! 77 | assert_equal referral, completed 78 | 79 | # Does not get called second time because referral was already completed 80 | completed = nil 81 | referral.complete! 82 | assert_nil completed 83 | ensure 84 | Refer.referral_completed = old_callback 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require_relative "../test/dummy/config/environment" 5 | ActiveRecord::Migrator.migrations_paths = [ File.expand_path("../test/dummy/db/migrate", __dir__) ] 6 | require "rails/test_help" 7 | 8 | # Load fixtures from the engine 9 | if ActiveSupport::TestCase.respond_to?(:fixture_paths=) 10 | ActiveSupport::TestCase.fixture_paths = [ File.expand_path("fixtures", __dir__) ] 11 | ActionDispatch::IntegrationTest.fixture_paths = ActiveSupport::TestCase.fixture_paths 12 | ActiveSupport::TestCase.file_fixture_path = File.expand_path("fixtures", __dir__) + "/files" 13 | ActiveSupport::TestCase.fixtures :all 14 | end 15 | --------------------------------------------------------------------------------