Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ PATH
specs:
code0-zero_track (0.0.0)
rails (>= 8.0.1)
zeitwerk (~> 2.7)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -82,11 +83,14 @@ GEM
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
binding_of_caller (1.0.1)
debug_inspector (>= 1.2.0)
builder (3.3.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.0)
crass (1.0.6)
date (3.4.1)
debug_inspector (1.2.0)
diff-lcs (1.6.0)
drb (2.2.1)
erubi (1.13.1)
Expand Down Expand Up @@ -147,6 +151,10 @@ GEM
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
proc_to_ast (0.2.0)
parser
rouge
unparser
psych (5.2.3)
date
stringio
Expand Down Expand Up @@ -195,6 +203,7 @@ GEM
regexp_parser (2.10.0)
reline (0.6.0)
io-console (~> 0.5)
rouge (4.5.1)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
Expand All @@ -207,6 +216,17 @@ GEM
rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-parameterized (1.0.2)
rspec-parameterized-core (< 2)
rspec-parameterized-table_syntax (< 2)
rspec-parameterized-core (1.0.1)
parser
proc_to_ast (>= 0.2.0)
rspec (>= 2.13, < 4)
unparser
rspec-parameterized-table_syntax (1.0.1)
binding_of_caller
rspec-parameterized-core (< 2)
rspec-rails (7.1.1)
actionpack (>= 7.0)
activesupport (>= 7.0)
Expand Down Expand Up @@ -253,6 +273,9 @@ GEM
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unparser (0.6.15)
diff-lcs (~> 1.3)
parser (>= 3.3.0)
uri (1.0.2)
useragent (0.16.11)
websocket-driver (0.7.7)
Expand All @@ -275,6 +298,7 @@ DEPENDENCIES
code0-zero_track!
rake (~> 13.0)
rspec (~> 3.0)
rspec-parameterized (~> 1.0)
rspec-rails (~> 7.0)
rubocop-rails (~> 2.19)
rubocop-rake (~> 0.7.0)
Expand Down
11 changes: 11 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/setup'
require 'code0/zero_track'

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
require 'irb'
ENV['IRB_USE_AUTOCOMPLETE'] = 'false'
IRB.start(__FILE__)
4 changes: 3 additions & 1 deletion code0-zero_track.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@ Gem::Specification.new do |spec|
spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = spec.homepage
spec.metadata['changelog_uri'] = "#{spec.homepage}/releases"
spec.metadata['rubygems_mfa_required'] = 'true'

spec.files = Dir.chdir(File.expand_path(__dir__)) do
Dir['{app,config,db,lib}/**/*', 'LICENSE', 'Rakefile', 'README.md']
end

spec.add_dependency 'rails', '>= 8.0.1'
spec.add_dependency 'zeitwerk', '~> 2.7'

spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'rspec-parameterized', '~> 1.0'
spec.add_development_dependency 'rspec-rails', '~> 7.0'
spec.add_development_dependency 'rubocop-rails', '~> 2.19'
spec.add_development_dependency 'rubocop-rake', '~> 0.7.0'
spec.add_development_dependency 'rubocop-rspec', '~> 3.0'
spec.add_development_dependency 'rubocop-rspec_rails', '~> 2.30'
spec.metadata['rubygems_mfa_required'] = 'true'
end
12 changes: 10 additions & 2 deletions lib/code0/zero_track.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# frozen_string_literal: true

require 'code0/zero_track/version'
require 'code0/zero_track/railtie'
require 'rails/railtie'

require 'zeitwerk'
loader = Zeitwerk::Loader.new
loader.tag = File.basename(__FILE__, '.rb')
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
loader.push_dir(File.expand_path(File.join(__dir__, '..')))
loader.setup

module Code0
module ZeroTrack
# Your code goes here...
end
end

Code0::ZeroTrack::Railtie # eager load the railtie
133 changes: 133 additions & 0 deletions lib/code0/zero_track/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# frozen_string_literal: true

module Code0
module ZeroTrack
class Context
LOG_KEY = 'meta'
CORRELATION_ID_KEY = 'correlation_id'
RAW_KEYS = [CORRELATION_ID_KEY].freeze

class << self
def with_context(attributes = {})
context = push(attributes)

begin
yield(context)
ensure
pop(context)
end
end

def push(new_attributes = {})
new_context = current&.merge(new_attributes) || new(new_attributes)

contexts.push(new_context)

new_context
end

def pop(context)
contexts.pop while contexts.include?(context)
end

def correlation_id
current&.correlation_id
end

def current
contexts.last
end

def log_key(key)
key = key.to_s
return key if RAW_KEYS.include?(key)
return key if key.start_with?("#{LOG_KEY}.")

"#{LOG_KEY}.#{key}"
end

private

def contexts
Thread.current[:labkit_contexts] ||= []
end
end

def initialize(values = {})
@data = {}

assign_attributes(values)
end

def merge(new_attributes)
new_context = self.class.new(data.dup)
new_context.assign_attributes(new_attributes)

new_context
end

def to_h
expand_data
end

def [](key)
to_h[log_key(key)]
end

def correlation_id
data[CORRELATION_ID_KEY]
end

def get_attribute(attribute)
raw = call_or_value(data[log_key(attribute)])

call_or_value(raw)
end

protected

def assign_attributes(attributes)
attributes = attributes.transform_keys(&method(:log_key))

data.merge!(attributes)

# Remove keys that had their values set to `nil` in the new attributes
data.keep_if { |_, value| valid_data?(value) }

# Assign a correlation if it was missing in the first context or when
# explicitly removed
data[CORRELATION_ID_KEY] ||= new_id

data
end

private

attr_reader :data

def log_key(key)
self.class.log_key(key)
end

def call_or_value(value)
value.respond_to?(:call) ? value.call : value
end

def expand_data
data.transform_values do |value|
value = call_or_value(value)

value if valid_data?(value)
end.compact
end

def new_id
SecureRandom.hex
end

def valid_data?(value)
value == false || value.present?
end
end
end
end
35 changes: 35 additions & 0 deletions lib/code0/zero_track/database/column_methods.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Code0
module ZeroTrack
module Database
module ColumnMethods
module Timestamps
# Appends columns `created_at` and `updated_at` to a table.
#
# It is used in table creation like:
# create_table 'users' do |t|
# t.timestamps_with_timezone
# end
def timestamps_with_timezone(**options)
options[:null] = false if options[:null].nil?

%i[created_at updated_at].each do |column_name|
column(column_name, :datetime_with_timezone, **options)
end
end

# Adds specified column with appropriate timestamp type
#
# It is used in table creation like:
# create_table 'users' do |t|
# t.datetime_with_timezone :did_something_at
# end
def datetime_with_timezone(column_name, **options)
column(column_name, :datetime_with_timezone, **options)
end
end
end
end
end
end
26 changes: 26 additions & 0 deletions lib/code0/zero_track/database/migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Code0
module ZeroTrack
module Database
class Migration
# rubocop:disable Naming/ClassAndModuleCamelCase
class V1_0 < ::ActiveRecord::Migration[7.1]
include Database::MigrationHelpers::AddColumnEnhancements
include Database::MigrationHelpers::ConstraintHelpers
include Database::MigrationHelpers::IndexHelpers
include Database::MigrationHelpers::TableEnhancements
end
# rubocop:enable Naming/ClassAndModuleCamelCase

def self.[](version)
version = version.to_s
name = "V#{version.tr('.', '_')}"
raise ArgumentError, "Invalid migration version: #{version}" unless const_defined?(name, false)

const_get(name, false)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Code0
module ZeroTrack
module Database
module MigrationHelpers
module AddColumnEnhancements
def add_column(table_name, column_name, type, *args, **kwargs, &block)
helper_context = self

limit = kwargs.delete(:limit)
unique = kwargs.delete(:unique)

super

return unless type == :text

quoted_column_name = helper_context.quote_column_name(column_name)

if limit
name = helper_context.send(:text_limit_name, table_name, column_name)

definition = "char_length(#{quoted_column_name}) <= #{limit}"

add_check_constraint(table_name, definition, name: name)
end

if unique.is_a?(Hash)
unique[:where] = "#{column_name} IS NOT NULL" if unique.delete(:allow_nil_duplicate)
column_name = "LOWER(#{quoted_column_name})" if unique.delete(:case_insensitive)

add_index table_name, column_name, unique: true, **unique
elsif unique
add_index table_name, column_name, unique: unique
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Code0
module ZeroTrack
module Database
module MigrationHelpers
module ConstraintHelpers
def text_limit_name(table, column, name: nil)
name.presence || check_constraint_name(table, column, 'max_length')
end

def check_constraint_name(table, column, type)
identifier = "#{table}_#{column}_check_#{type}"
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)

"check_#{hashed_identifier}"
end
end
end
end
end
end
Loading