Skip to content

Commit

Permalink
More effective way to store uuids (#8)
Browse files Browse the repository at this point in the history
* More effective way to store uuids

* Add github actions

* More helpers for automated v1 migrations

* Fixes for ruby 3.1
  • Loading branch information
gsmetal committed Feb 27, 2024
1 parent e6d89ea commit 8db8f62
Show file tree
Hide file tree
Showing 18 changed files with 321 additions and 75 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/gem-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Build + publish

on:
push:
tags: [ v* ]
jobs:
build:
name: Build + Publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1

- name: Release Gem
if: contains(github.ref, 'refs/tags/v')
uses: cadwallion/publish-rubygems-action@master
env:
RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
RELEASE_COMMAND: bundle exec rake release
# steps:
# - uses: actions/checkout@v3
# - name: Set up Ruby
# uses: ruby/setup-ruby@v1
# with:
# ruby-version: .ruby-version
# bundler-cache: true
37 changes: 37 additions & 0 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby

name: Test

on:
push:
branches: [ '*' ]

permissions:
contents: read

jobs:
test:
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
os: [ubuntu, macos]
ruby-version: ['2.7', '3.0', '3.1']

steps:
- uses: actions/checkout@v3
- name: Set up Ruby
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
# change this to (see https://github.com/ruby/setup-ruby#versioning):
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run rubocop
run: bundle exec rubocop
- name: Run tests
run: bundle exec rake
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/pkg/
/spec/reports/
/tmp/
/.history/
9 changes: 6 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
Metrics/LineLength:
Max: 120
AllCops:
NewCops: disable

Layout/LineLength:
Max: 140

Metrics/CyclomaticComplexity:
Max: 10

Metrics/MethodLength:
Max: 20
Max: 30

Metrics/AbcSize:
Max: 25
Expand Down
15 changes: 0 additions & 15 deletions .travis.yml

This file was deleted.

2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

source 'https://rubygems.org'

# Specify your gem's dependencies in uuidable.gemspec
Expand Down
78 changes: 44 additions & 34 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
PATH
remote: .
specs:
uuidable (0.2.6)
activerecord (>= 4.2, < 7.1)
uuidable (1.0.0)
activerecord (>= 4.2, < 7.2)
mysql-binuuid-rails (>= 1.3, < 2)
uuidtools (>= 2.1, < 3)

GEM
Expand All @@ -18,11 +19,12 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.4.0)
ast (2.3.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
backports (3.6.8)
concurrent-ruby (1.1.10)
diff-lcs (1.2.5)
diff-lcs (1.5.0)
ethon (0.8.1)
ffi (>= 1.3.0)
faraday (0.9.2)
Expand All @@ -38,47 +40,55 @@ GEM
net-http-persistent (>= 2.7)
net-http-pipeline
highline (1.7.8)
i18n (1.11.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
json (2.3.1)
launchy (2.4.3)
addressable (~> 2.3)
minitest (5.16.2)
multi_json (1.11.2)
multipart-post (2.0.0)
mysql-binuuid-rails (1.3.0)
activerecord (>= 5)
net-http-persistent (2.9.4)
net-http-pipeline (1.0.1)
parallel (1.12.0)
parser (2.4.0.2)
ast (~> 2.3)
powerpack (0.1.1)
parallel (1.22.1)
parser (3.1.2.0)
ast (~> 2.4.1)
public_suffix (4.0.7)
pusher-client (0.6.2)
json
websocket (~> 1.0)
rainbow (2.2.2)
rake
rake (13.0.1)
rspec (3.4.0)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-core (3.4.3)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.5.0)
rexml (3.2.5)
rspec (3.11.0)
rspec-core (~> 3.11.0)
rspec-expectations (~> 3.11.0)
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-mocks (3.4.1)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)
rubocop (0.50.0)
rspec-support (~> 3.11.0)
rspec-support (3.11.0)
rubocop (1.32.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 2.2.2, < 3.0)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.19.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.9.0)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.19.1)
parser (>= 3.1.1.0)
ruby-progressbar (1.11.0)
travis (1.8.2)
backports
faraday (~> 0.9)
Expand All @@ -90,22 +100,22 @@ GEM
typhoeus (~> 0.6, >= 0.6.8)
typhoeus (0.8.0)
ethon (>= 0.8.0)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (1.3.0)
unicode-display_width (2.2.0)
uuidtools (2.2.0)
websocket (1.2.2)

PLATFORMS
ruby

DEPENDENCIES
bundler (~> 1.11)
bundler (~> 2.4)
rake (~> 13.0)
rspec (~> 3.0)
rubocop
travis (~> 1.8, >= 1.8.2)
uuidable!

BUNDLED WITH
1.17.3
2.4.22
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Uuidable [![Build Status](https://travis-ci.org/flant/uuidable.svg?branch=master)](https://travis-ci.org/flant/uuidable)
# Uuidable

[![Build](https://github.com/flant/uuidable/actions/workflows/ruby.yml/badge.svg)](https://github.com/flant/uuidable/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/uuidable.svg)](https://badge.fury.io/rb/uuidable)

With this gem you can use UUID instead of id in routes. But id is still primary key.

Expand Down
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

Expand Down
1 change: 1 addition & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/setup'
require 'uuidable'
Expand Down
7 changes: 6 additions & 1 deletion lib/uuidable.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# frozen_string_literal: true

require 'uuidable/version'
require 'active_support'
require 'active_model'
require 'uuidtools'

require 'mysql-binuuid-rails'

# Main module
module Uuidable
module_function
Expand All @@ -12,4 +16,5 @@ def generate_uuid
end

require 'uuidable/migration'
require 'uuidable/v1_migration_helpers'
require 'uuidable/active_record'
27 changes: 22 additions & 5 deletions lib/uuidable/active_record.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module Uuidable
# ActiveRecord mixin
module ActiveRecord
Expand All @@ -7,7 +9,7 @@ class UuidChangeError < RuntimeError; end

module Finder
def find(*args)
if args.first && args.first.is_a?(String) && args.first.match(UUIDTools::UUID_REGEXP)
if args.first.is_a?(String) && args.first&.match(UUIDTools::UUID_REGEXP)
find_by_uuid!(*args)
else
super
Expand All @@ -19,8 +21,23 @@ def find(*args)
module ClassMethods
include Finder

def uuidable(as_param: true)
after_initialize { self.uuid = Uuidable.generate_uuid if attributes.keys.include?('uuid') && uuid.blank? }
def uuidable(as_param: true) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# Configure all uuid columns for MySQL. Database may not be connected (i.e. on assets precompile), so we must supress errors.
conn_config = respond_to?(:connection_db_config) ? connection_db_config.configuration_hash : connection_config
if conn_config[:adapter].include?('mysql')
begin
columns.select { |c| c.type == :binary && c.limit == 16 && c.name.include?('uuid') }.each do |column|
attribute column.name.to_sym, MySQLBinUUID::Type.new
end
rescue ::ActiveRecord::ConnectionNotEstablished, Mysql2::Error::ConnectionError, ::ActiveRecord::NoDatabaseError # rubocop:disable Lint/SuppressedException
end
end

after_initialize do
self.uuid = Uuidable.generate_uuid if attributes.keys.include?('uuid') && uuid.blank?
self.uuid__old = uuid if respond_to?(:uuid__old)
end

validates :uuid, presence: true, uniqueness: true, if: :uuid_changed?

if as_param
Expand All @@ -44,6 +61,6 @@ def short_uuid
end

ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.send(:include, Uuidable::ActiveRecord)
ActiveRecord::Relation.send(:prepend, Uuidable::ActiveRecord::Finder)
ActiveRecord::Base.include Uuidable::ActiveRecord
ActiveRecord::Relation.prepend Uuidable::ActiveRecord::Finder
end
24 changes: 13 additions & 11 deletions lib/uuidable/migration.rb
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
# frozen_string_literal: true

module Uuidable
COLUMN_NAME = :uuid
COLUMN_TYPE = :binary
COLUMN_OPTIONS = { limit: 36, null: false }.freeze
COLUMN_OPTIONS = { limit: 16, null: false }.freeze
INDEX_OPTIONS = { unique: true }.freeze

# Module adds method to table definition
module TableDefinition
def uuid(opts = {})
def uuid(column_name = COLUMN_NAME, **opts)
index_opts = opts.delete(:index)
index_opts = {} if index_opts.nil?

column_name = opts.delete(:column_name) || COLUMN_NAME
column_name ||= opts.delete(:column_name)

column column_name, COLUMN_TYPE, COLUMN_OPTIONS.merge(opts)
index column_name, INDEX_OPTIONS.merge(index_opts) if index_opts
column column_name, COLUMN_TYPE, **COLUMN_OPTIONS.merge(opts)
index column_name, **INDEX_OPTIONS.merge(index_opts) if index_opts
end
end

# Module adds method to alter table migration
module Migration
def add_uuid_column(table_name, opts = {})
def add_uuid_column(table_name, column_name = COLUMN_NAME, **opts)
index_opts = opts.delete(:index)
index_opts = {} if index_opts == true

column_name = opts.delete(:column_name) || COLUMN_NAME
column_name ||= opts.delete(:column_name)

add_column table_name, column_name, COLUMN_TYPE, COLUMN_OPTIONS.merge(opts)
add_column table_name, column_name, COLUMN_TYPE, **COLUMN_OPTIONS.merge(opts)

add_uuid_index(table_name, index_opts.merge(column_name: column_name)) if index_opts
end

def add_uuid_index(table_name, opts = {})
column_name = opts.delete(:column_name) || COLUMN_NAME

add_index table_name, column_name, INDEX_OPTIONS.merge(opts)
add_index table_name, column_name, **INDEX_OPTIONS.merge(opts)
end
end
end

if defined? ActiveRecord::ConnectionAdapters::TableDefinition
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, Uuidable::TableDefinition
ActiveRecord::ConnectionAdapters::TableDefinition.include Uuidable::TableDefinition
end

ActiveRecord::Migration.send :include, Uuidable::Migration if defined? ActiveRecord::Migration
ActiveRecord::Migration.include Uuidable::Migration if defined? ActiveRecord::Migration
Loading

0 comments on commit 8db8f62

Please sign in to comment.