Skip to content

Instantly share code, notes, and snippets.

@noelrappin
Last active May 8, 2024 18:41
Show Gist options
  • Save noelrappin/04137d919b96f9786cdfcd094df005cf to your computer and use it in GitHub Desktop.
Save noelrappin/04137d919b96f9786cdfcd094df005cf to your computer and use it in GitHub Desktop.
Gist for Let's Build A Gem With Rails
bundle gem change_ledger --coc --mit --exe --test=rspec --ci=github --linter=standard
# in change_ledger.gemspec
spec.add_dependency "rails", ">= 7.0.0"
# in change_ledger.gemspec
add_development_dependency "rspec-rails", "~> 3.1.0"
#spec/lib/change_ledger/ledger_spec.rb
require "time"
module ChangeLedger
Dummy = Data.define(:id, :name) do
def to_global_id = "dummy_#{id}"
def attributes = {"id" => id, "name" => name}
end
RSpec.describe Ledger do
it "creates rows" do
instance = Dummy.new(id: 3, name: "Noel")
time = Time.parse("May 5, 2024 2:30 pm")
travel_to(time) do
result = Ledger.entry(instance, :name)
expect(result).to have_attributes(
id: a_truthy_value,
global_id: "dummy_3",
target: "name",
value: "Noel",
changed_at: time
)
end
end
end
# lib/change_ledger/ledger.rb
module ChangeLedger
class Ledger
end
end
# in lib/change_ledger.rb
require_relative "change_ledger/ledger"
# change_ledger.gemspec
add_dependency "zeitwerk"
# lib/change_ledger.rb
require "zeitwerk"
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
loader.setup
# spec/spec_helper.rb
# at the top of the file
require "active_support/all"
require "active_support/testing/time_helpers"
require "active_record"
# in the config block
config.include ActiveSupport::Testing::TimeHelpers
# lib/change_ledger/ledger.rb
module ChangeLedger
class Ledger < ActiveRecord::Base
def self.entry(instance, target)
create(
global_id: instance.to_global_id,
target: target.to_s,
value: instance.attributes[target.to_s],
changed_at: Time.current
)
end
end
end
# spec/lib/change_ledger/ledger_spec.rb
it "creates rows" do
instance = Dummy.new(id: 3, name: "Noel")
time = Time.parse("May 5, 2024 2:30 pm")
allow(Ledger).to receive(:create)
travel_to(time) do
result = Ledger.entry(instance, :name)
expect(Ledger).to have_received(:create).with(
global_id: "dummy_3",
target: :name,
value: "Noel",
changed_at: time
)
end
end
# change_ledger.gemspec
spec.add_development_dependency "sqlite3", "~> 1.7"
# spec/spec_helper.rb
# bottom of file
ActiveRecord::Base.establish_connection(
adapter: "sqlite3",
database: ":memory:"
)
load("#{__dir__}/schema.rb")
# spec/schema.rb
ActiveRecord::Schema.define do
self.verbose = false
create_table :ledgers, force: true do |t|
t.string :global_id, null: false
t.string :target, null: false
t.string :value, null: false
t.datetime :changed_at, null: false
t.timestamps
end
end
# spec/lib/change_ledger/ledger_spec.rb
RSpec.describe Ledger do
it "creates rows" do
instance = Dummy.new(id: 3, name: "Noel")
time = Time.parse("May 5, 2024 2:30 pm")
travel_to(time) do
result = Ledger.entry(instance, :name)
expect(result).to have_attributes(
id: a_truthy_value,
global_id: "dummy_3",
target: "name",
value: "Noel",
changed_at: time
)
end
end
end
# change_ledger.gemspec
spec.add_development_dependency "combustion"
# spec/spec_helper.rb
# remove the things added in the last step
# top of file
require "combustion"
Combustion.initialize! :all
# this would go in lib/change_ledger/railtie.rb, but we're not acutally going to do it
module ChangeLedger
class Railtie < Rails::Railtie
initializer "change_ledger.configure" do |app|
if File.exist?(".change_ledger.yml")
Configuration.from_file(".change_ledger.yml")
end
end
end
end
# lib/generators/change_ledger/migration_generator.rb
require "rails/generators"
require "rails/generators/active_record"
module ChangeLedger
class MigrationGenerator < Rails::Generators::Base
include ActiveRecord::Generators::Migration
source_root File.expand_path("templates", __dir__)
def copy_migration
migration_template "migration.rb.erb", "db/migrate/add_ledgers.rb"
end
end
end
# lib/generators/change_ledger/templates/migration.rb.erb
# frozen_string_literal: true
class AddLedgerTable < ActiveRecord::Migration<%= "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" %>
def change
create_table :ledgers do |t|
t.string :global_id
t.string :target
t.string :value
t.datetime :changed_at
t.timestamps null: false
end
end
add_index :ledgers, :target
add_index :ledgers, :global_id
end
# in the Gemfile of another Rails app
gem "change_ledger", path: "../change_ledger"
# in the routes file
# then from the command line
$ rails generate help
$ rails generate change_ledger:migration
# lib/change_ledger/engine.rb
module ChangeLedger
class Engine < ::Rails::Engine
isolate_namespace ChangeLedger
end
end
ChangeLedger::Engine.routes.draw do
root "home#show"
end
# lib/change_ledger/home_controller.rb
module ChangeLedger
class HomeController < ActionController::Base
def show
render inline: "Hi"
end
end
end
# config/routes.rb of using Rails app
mount ChangeLedger::Engine => "/ledger"
appraise "rails-7" do
gem "rails", "~> 7.0.0"
end
appraise "rails-71" do
gem "rails", "~> 7.1.0"
end
$ bundle exec appraisal install
# then
$ bundle exec appraisal rspec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment