├── .circleci └── config.yml ├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── backend │ └── backend.rb ├── stale_options.rb └── stale_options │ ├── abstract_options.rb │ ├── array_options.rb │ ├── object_options.rb │ ├── relation_options.rb │ └── version.rb ├── stale_options.gemspec └── test ├── backend └── backend_test.rb ├── fixtures └── test.sqlite3 ├── stale_options_test.rb ├── support └── test_model.rb └── test_helper.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | coveralls: coveralls/coveralls@2.1.0 5 | node: circleci/node@5.1.0 6 | 7 | workflows: 8 | tests: 9 | jobs: 10 | - test: 11 | matrix: 12 | parameters: 13 | ruby-image: ["ruby:2.7", "ruby:3.0", "ruby:3.1", "ruby:3.2"] 14 | 15 | coveralls: 16 | jobs: 17 | - coveralls 18 | 19 | jobs: 20 | test: 21 | parameters: 22 | ruby-image: 23 | type: string 24 | docker: 25 | - image: << parameters.ruby-image >> 26 | steps: 27 | - checkout 28 | - run: 29 | name: Install dependencies 30 | command: bundle install 31 | - run: 32 | name: Run tests 33 | command: bundle exec rake test 34 | 35 | coveralls: 36 | docker: 37 | - image: "ruby:3.2" 38 | steps: 39 | - checkout 40 | - run: 41 | name: Install dependencies 42 | command: bundle install 43 | - run: 44 | name: Run tests 45 | command: bundle exec rake test 46 | - node/install 47 | - coveralls/upload: 48 | coverage_format: simplecov 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /.bundle/ 3 | /.yardoc 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | Gemfile.lock 11 | gemfiles/*.gemfile.lock 12 | gemfiles/.bundle 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in stale_options.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Nikolay Digaev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StaleOptions 2 | 3 | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/digaev/stale_options/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/digaev/stale_options/tree/master) 4 | [![Coverage Status](https://coveralls.io/repos/github/digaev/stale_options/badge.svg?branch=master)](https://coveralls.io/github/digaev/stale_options?branch=master) 5 | [![Gem Version](https://badge.fury.io/rb/stale_options.svg)](https://badge.fury.io/rb/stale_options) 6 | 7 | A gem for caching HTTP responses. 8 | 9 | The gem was built with an idea to implement a class which will create options for the `ActionController::ConditionalGet#stale?` method. It allows to cache any kind of objects, not only records or collections (unlike of `#stale?`). 10 | 11 | ___ 12 | 13 | * [Installation](#installation) 14 | * [Usage](#usage) 15 | * [Caching options](#caching-options) 16 | * [Examples](#examples) 17 | * [Controller helpers](#controller-helpers) 18 | * [Contributing](#contributing) 19 | * [License](#license) 20 | 21 | ## Installation 22 | 23 | Add one of these lines to your application's Gemfile depending on your Rails version: 24 | 25 | ```ruby 26 | # Rails 5.2 27 | gem 'stale_options', '~> 1.0.0' 28 | 29 | # Rails 6.0 30 | gem 'stale_options', '~> 1.1.0' 31 | 32 | # Rails 6.1 33 | gem 'stale_options', '~> 1.2.0' 34 | 35 | # Rails 7.0 36 | gem 'stale_options', '~> 1.3.0' 37 | ``` 38 | 39 | And then execute: 40 | 41 | ```sh 42 | bundle install 43 | ``` 44 | 45 | ## Usage 46 | 47 | ``` 48 | StaleOptions.create(Item.all) 49 | => {:etag=>"39f08c583b023142dd64b0922dfaefd4", :last_modified=>2018-07-04 18:05:22 UTC} 50 | ``` 51 | 52 | The method accepts an optional second parameter (see Caching options). 53 | 54 | ### Caching options 55 | 56 | There are two options for caching, `cache_by` and `last_modified`: 57 | 58 | * `:cache_by` 59 | * `String` or `Symbol`. The name of the method that returns the unique identifier of the object for caching, which is used to set `etag` of the resulting options. Set it to `itself` if you don't have such a method (see example below) and the gem will generate `etag` for you. 60 | * Default: `:updated_at`. 61 | * `:last_modified` 62 | * `String` or `Symbol`. The name of the method that returns an instance of `ActiveSupport::TimeWithZone`, `DateTime` or `Time`. 63 | * `ActiveSupport::TimeWithZone`, `DateTime`, `Time` or `nil` to set `:last_modified` directly. 64 | * Default: `:updated_at`. 65 | 66 | ### Examples 67 | 68 | ``` 69 | StaleOptions.create(Task.all, last_modified: :done_at) 70 | => {:etag=>"ce8d2fbc9b815937b59e8815d8a85c21", :last_modified=>2018-06-30 18:25:07 UTC} 71 | 72 | StaleOptions.create([1, 2, 3], cache_by: :itself, last_modified: nil) 73 | => {:etag=>"73250e72da5d8950b6bbb16044353d26", :last_modified=>nil} 74 | 75 | StaleOptions.create({ a: 'a', b: 'b', c: 'c' }, cache_by: :itself, last_modified: Time.now) 76 | => {:etag=>"fec76eca1192bc7371e44d517b56c93f", :last_modified=>2018-07-08 07:08:48 UTC} 77 | ``` 78 | 79 | ### Controller helpers 80 | 81 | In your controller: 82 | 83 | ```ruby 84 | # To render a template: 85 | 86 | class PostsController < ApplicationController 87 | include StaleOptions::Backend 88 | 89 | def index 90 | if_stale?(Post.all) do |posts| 91 | @posts = posts 92 | end 93 | end 94 | end 95 | 96 | # Or, to render json: 97 | 98 | class PostsController < ApplicationController 99 | include StaleOptions::Backend 100 | 101 | def index 102 | if_stale?(Post.all) do |posts| 103 | render json: posts 104 | end 105 | end 106 | end 107 | ``` 108 | 109 | Here we're using the `#if_stale?` method which was added to the controller by including the `StaleOptions::Backend` module. The method accepts two arguments `record` and `options` (yeah, just like `StaleOptions.create`). 110 | 111 | Under the hood, it calls `ActionController::ConditionalGet#stale?` with options created by `StaleOptions.create`: 112 | 113 | ```ruby 114 | def if_stale?(record, options = {}) 115 | yield(record) if stale?(StaleOptions.create(record, options)) 116 | end 117 | ``` 118 | 119 | ## Contributing 120 | 121 | Bug reports and pull requests are welcome on GitHub at https://github.com/digaev/stale_options. 122 | 123 | ## License 124 | 125 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 126 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList["test/**/*_test.rb"] 8 | end 9 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "stale_options" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/backend/backend.rb: -------------------------------------------------------------------------------- 1 | module StaleOptions 2 | module Backend 3 | protected 4 | 5 | # Usage: 6 | # 7 | # class ItemsController < ApplicationController 8 | # include StaleOptions::Backend 9 | # 10 | # def index 11 | # if_stale?(Item.all) do |items| 12 | # render json: items 13 | # end 14 | # end 15 | # end 16 | # 17 | def if_stale?(record, options = {}) 18 | options = StaleOptions.create(record, **options) 19 | 20 | if stale?(**options) 21 | block_given? ? yield(record) : true 22 | end 23 | end 24 | 25 | def unless_stale?(record, options = {}) 26 | options = StaleOptions.create(record, **options) 27 | 28 | unless stale?(**options) 29 | block_given? ? yield(record) : true 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/stale_options.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'active_support/time' 3 | 4 | require 'backend/backend' 5 | 6 | module StaleOptions 7 | autoload :AbstractOptions, 'stale_options/abstract_options' 8 | autoload :ArrayOptions, 'stale_options/array_options' 9 | autoload :ObjectOptions, 'stale_options/object_options' 10 | autoload :RelationOptions, 'stale_options/relation_options' 11 | 12 | def self.create(record, options = {}) 13 | klass = 14 | case record 15 | when ActiveRecord::Relation 16 | RelationOptions 17 | when Array 18 | ArrayOptions 19 | else 20 | ObjectOptions 21 | end 22 | 23 | klass.new(record, options).to_h 24 | end 25 | 26 | def self.time?(obj) 27 | case obj 28 | when ActiveSupport::TimeWithZone, DateTime, Time 29 | true 30 | else 31 | false 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/stale_options/abstract_options.rb: -------------------------------------------------------------------------------- 1 | module StaleOptions 2 | class AbstractOptions 3 | def initialize(record, options = {}) 4 | @record = record 5 | @options = { 6 | cache_by: :updated_at, 7 | last_modified: :updated_at 8 | }.merge!(options) 9 | end 10 | 11 | def to_h 12 | { etag: etag, last_modified: nil }.tap do |h| 13 | unless last_modified_opt.nil? 14 | h[:last_modified] = StaleOptions.time?(last_modified_opt) ? last_modified_opt : last_modified 15 | h[:last_modified] = h[:last_modified]&.utc 16 | end 17 | end 18 | end 19 | 20 | private 21 | 22 | def cache_by_opt 23 | @options[:cache_by] 24 | end 25 | 26 | def cache_by_itself? 27 | cache_by_opt.to_s == 'itself' 28 | end 29 | 30 | def read_cache_by(obj) 31 | value = obj.public_send(cache_by_opt) 32 | 33 | StaleOptions.time?(value) ? value.to_f : value 34 | end 35 | 36 | def last_modified_opt 37 | @options[:last_modified] 38 | end 39 | 40 | def read_last_modified(obj) 41 | obj.public_send(last_modified_opt) 42 | end 43 | 44 | def object_hash(obj) 45 | Digest::MD5.hexdigest(Marshal.dump(obj)) 46 | end 47 | 48 | def collection_hash(collection) 49 | object_hash(collection.map { |obj| read_cache_by(obj) }) 50 | end 51 | 52 | protected 53 | 54 | def etag 55 | raise NotImplementedError 56 | end 57 | 58 | def last_modified 59 | raise NotImplementedError 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/stale_options/array_options.rb: -------------------------------------------------------------------------------- 1 | module StaleOptions 2 | class ArrayOptions < AbstractOptions 3 | private 4 | 5 | def most_recent 6 | @most_recent ||= @record.max do |a, b| 7 | read_last_modified(a) <=> read_last_modified(b) 8 | end 9 | end 10 | 11 | protected 12 | 13 | def etag 14 | cache_by_itself? ? object_hash(@record) : collection_hash(@record) 15 | end 16 | 17 | def last_modified 18 | read_last_modified(most_recent) if most_recent 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/stale_options/object_options.rb: -------------------------------------------------------------------------------- 1 | module StaleOptions 2 | class ObjectOptions < AbstractOptions 3 | protected 4 | 5 | def etag 6 | object_hash(read_cache_by(@record)) if @record 7 | end 8 | 9 | def last_modified 10 | read_last_modified(@record) if @record 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/stale_options/relation_options.rb: -------------------------------------------------------------------------------- 1 | module StaleOptions 2 | class RelationOptions < AbstractOptions 3 | protected 4 | 5 | def etag 6 | cache_by_itself? ? object_hash(@record.to_a) : collection_hash(@record) 7 | end 8 | 9 | def last_modified 10 | # FIXME: ActiveRecord#maximum ignores order, 11 | # so we can't just say `@record.maximum(last_modified_opt)`. 12 | # See: https://stackoverflow.com/questions/23243828/rails-activerecord-maximumcolumn-ignores-order 13 | 14 | @record.pluck(last_modified_opt).max 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/stale_options/version.rb: -------------------------------------------------------------------------------- 1 | module StaleOptions 2 | VERSION = '1.3.0'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /stale_options.gemspec: -------------------------------------------------------------------------------- 1 | 2 | lib = File.expand_path('lib', __dir__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'stale_options/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'stale_options' 8 | spec.version = StaleOptions::VERSION 9 | spec.authors = ['Nikolay Digaev'] 10 | spec.email = ['ffs.cmp@gmail.com'] 11 | 12 | spec.summary = 'A gem for caching HTTP responses' 13 | spec.homepage = 'https://github.com/digaev/stale_options' 14 | spec.license = 'MIT' 15 | 16 | # Specify which files should be added to the gem when it is released. 17 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 18 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 19 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 20 | end 21 | 22 | spec.require_paths = ['lib'] 23 | 24 | spec.add_dependency 'activerecord', '~> 7.0.0' 25 | spec.add_dependency 'activesupport', '~> 7.0.0' 26 | 27 | spec.add_development_dependency 'minitest', '~> 5.18.0' 28 | spec.add_development_dependency 'rake', '~> 13.0.0' 29 | spec.add_development_dependency 'simplecov' 30 | spec.add_development_dependency 'sqlite3', '~> 1.6.0' 31 | end 32 | -------------------------------------------------------------------------------- /test/backend/backend_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BackendTest < Minitest::Test 4 | def test_if_stale_true 5 | options = { etag: '123' }.freeze 6 | 7 | StaleOptions.stub :create, options do 8 | mock = Minitest::Mock.new 9 | mock.expect(:stale?, true, **options) 10 | 11 | block_called = false 12 | 13 | class << mock 14 | include StaleOptions::Backend 15 | end 16 | 17 | mock.send(:if_stale?, 0) do |record| 18 | block_called = true 19 | assert_equal record, 0 20 | end 21 | 22 | assert_mock mock 23 | assert block_called 24 | end 25 | end 26 | 27 | def test_if_stale_true_without_block 28 | options = { etag: '123' }.freeze 29 | 30 | StaleOptions.stub :create, options do 31 | mock = Minitest::Mock.new 32 | mock.expect(:stale?, true, **options) 33 | 34 | class << mock 35 | include StaleOptions::Backend 36 | end 37 | 38 | assert mock.send(:if_stale?, 0) 39 | assert_mock mock 40 | end 41 | end 42 | 43 | def test_if_stale_false 44 | options = { etag: '123' }.freeze 45 | 46 | StaleOptions.stub :create, options do 47 | mock = Minitest::Mock.new 48 | mock.expect(:stale?, false, **options) 49 | 50 | class << mock 51 | include StaleOptions::Backend 52 | end 53 | 54 | mock.send(:if_stale?, 0) do |_| 55 | raise 'Should not be called!' 56 | end 57 | 58 | assert_mock mock 59 | end 60 | end 61 | 62 | def test_unless_stale_true 63 | options = { etag: '123' }.freeze 64 | 65 | StaleOptions.stub :create, options do 66 | mock = Minitest::Mock.new 67 | mock.expect(:stale?, false, **options) 68 | 69 | block_called = false 70 | 71 | class << mock 72 | include StaleOptions::Backend 73 | end 74 | 75 | mock.send(:unless_stale?, 0) do |record| 76 | block_called = true 77 | assert_equal record, 0 78 | end 79 | 80 | assert_mock mock 81 | assert block_called 82 | end 83 | end 84 | 85 | def test_unless_stale_true_without_block 86 | options = { etag: '123' }.freeze 87 | 88 | StaleOptions.stub :create, options do 89 | mock = Minitest::Mock.new 90 | mock.expect(:stale?, false, **options) 91 | 92 | class << mock 93 | include StaleOptions::Backend 94 | end 95 | 96 | assert mock.send(:unless_stale?, 0) 97 | assert_mock mock 98 | end 99 | end 100 | 101 | def test_unless_stale_false 102 | options = { etag: '123' }.freeze 103 | 104 | StaleOptions.stub :create, options do 105 | mock = Minitest::Mock.new 106 | mock.expect(:stale?, true, **options) 107 | 108 | class << mock 109 | include StaleOptions::Backend 110 | end 111 | 112 | mock.send(:unless_stale?, 0) do |_| 113 | raise 'Should not be called!' 114 | end 115 | 116 | assert_mock mock 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /test/fixtures/test.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digaev/stale_options/093916d3f09bf6a9ea25fbb7099793b77c6b767c/test/fixtures/test.sqlite3 -------------------------------------------------------------------------------- /test/stale_options_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class StaleOptionsTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::StaleOptions::VERSION 6 | end 7 | 8 | def test_create_returns_hash 9 | h = StaleOptions.create(nil) 10 | 11 | assert h.is_a?(Hash) 12 | assert_equal h.keys.length, 2 13 | 14 | assert h.key?(:etag) 15 | assert h.key?(:last_modified) 16 | end 17 | 18 | def test_create_with_nil 19 | h = StaleOptions.create(nil) 20 | 21 | assert_nil h[:etag] 22 | assert_nil h[:last_modified] 23 | end 24 | 25 | def test_create_with_options 26 | mock = Minitest::Mock.new 27 | mock.expect(:itself, 0) 28 | mock.expect(:created_at, Time.now) 29 | 30 | StaleOptions.create(mock, cache_by: :itself, last_modified: :created_at) 31 | 32 | assert_mock mock 33 | end 34 | 35 | def test_create_with_last_modified_nil 36 | h = StaleOptions.create(0, cache_by: :itself, last_modified: nil) 37 | 38 | assert_nil h[:last_modified] 39 | end 40 | 41 | def test_create_with_last_modified_time 42 | t = Time.now 43 | h = StaleOptions.create(0, cache_by: :itself, last_modified: t) 44 | 45 | assert_equal h[:last_modified], t 46 | end 47 | 48 | def test_create_with_empty_array 49 | h = StaleOptions.create([]) 50 | 51 | assert h[:etag].is_a?(String) 52 | assert_nil h[:last_modified] 53 | end 54 | 55 | def test_create_with_non_empty_array 56 | array = Array.new(3) { |i| TestModel.new(updated_at: Time.now - i.minutes) } 57 | h = StaleOptions.create(array) 58 | 59 | assert h[:etag].is_a?(String) 60 | assert_equal h[:last_modified], array.first.updated_at 61 | end 62 | 63 | def test_create_with_non_empty_simple_array 64 | h = StaleOptions.create([1, 2, 3], cache_by: :itself, last_modified: nil) 65 | 66 | assert h[:etag].is_a?(String) 67 | assert_nil h[:last_modified] 68 | end 69 | 70 | def test_create_with_empty_relation 71 | h = StaleOptions.create(TestModel.where(id: nil)) 72 | 73 | assert h[:etag].is_a?(String) 74 | assert_nil h[:last_modified] 75 | end 76 | 77 | def test_create_with_non_empty_relation 78 | h = StaleOptions.create(TestModel.all) 79 | most_recent = TestModel.most_recent.first 80 | 81 | assert h[:etag].is_a?(String) 82 | assert_equal h[:last_modified], most_recent.updated_at 83 | end 84 | 85 | def test_time? 86 | assert StaleOptions.time?(DateTime.now) 87 | assert StaleOptions.time?(Time.current) 88 | assert StaleOptions.time?(Time.now) 89 | 90 | assert_equal StaleOptions.time?(nil), false 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /test/support/test_model.rb: -------------------------------------------------------------------------------- 1 | class TestModel < ActiveRecord::Base 2 | self.table_name = 'test_models' 3 | 4 | attribute :created_at, :datetime 5 | attribute :updated_at, :datetime 6 | 7 | scope :most_recent, -> { order(created_at: :desc) } 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 2 | 3 | require 'simplecov' 4 | 5 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ 6 | SimpleCov::Formatter::HTMLFormatter, 7 | SimpleCov::Formatter::SimpleFormatter, 8 | ]) 9 | SimpleCov.start 10 | 11 | require 'stale_options' 12 | require 'minitest/autorun' 13 | 14 | ActiveRecord::Base.establish_connection( 15 | adapter: 'sqlite3', 16 | database: 'test/fixtures/test.sqlite3' 17 | ) 18 | 19 | Dir["#{Dir.pwd}/test/support/**/*.rb"].each { |f| require f } 20 | --------------------------------------------------------------------------------