Skip to content

Instantly share code, notes, and snippets.

@palkan
Created December 24, 2021 14:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save palkan/b8b6e8b310030acc6d4660fc5de8c790 to your computer and use it in GitHub Desktop.
Save palkan/b8b6e8b310030acc6d4660fc5de8c790 to your computer and use it in GitHub Desktop.
ActiveModel .normalize
# frozen_string_literal: true
require "bundler/inline"
gemfile(true, quiet: true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "activesupport", "~> 7.0.0", require: false
gem "activerecord", "~> 7.0.0", require: false
gem "sqlite3"
end
require "active_support"
require "active_record"
require "minitest/autorun"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.string :email
t.string :alive
end
end
class NormalizedType < DelegateClass(ActiveRecord::Type::Value)
private attr_reader :normalizer, :allow_nil
def initialize(subtype, normalizer, allow_nil: true)
@normalizer = normalizer
@allow_nil = allow_nil
super(subtype)
end
def cast(value)
super(value).then do |casted|
next if casted.nil? && allow_nil
normalizer.call(casted)
end
end
def serialize(value)
return super(value) if value.nil? && allow_nil
super(normalizer.call(value))
end
def deserialize(value)
super(value).then do |casted|
next if casted.nil? && allow_nil
normalizer.call(casted)
end
end
end
ActiveRecord::Base.extend(Module.new do
def normalize(attr_name, proc, **options)
attribute(attr_name) do
NormalizedType.new(_1, proc, **options)
end
end
end)
class RawUser < ActiveRecord::Base
self.table_name = "users"
end
class User < ActiveRecord::Base
normalize :email, -> { _1.strip.downcase.presence }, allow_nil: true
attribute :alive, :boolean
validate :email_is_normalized
private
def email_is_normalized
errors.add(:email, "has been normalized") unless email.match?(/[a-z1-9\.@\-]/)
end
end
class SomeTest < Minitest::Test
def setup
ActiveRecord::Base.connection.execute "DELETE FROM users;"
end
def test_database
user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
assert_equal "cruise-control@example.com", user.email
reloaded_user = User.find(user.id)
assert_equal "cruise-control@example.com", reloaded_user.email
end
def test_from_user
user = User.new(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
assert_equal "cruise-control@example.com", user.email
end
def test_queries
user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
assert_equal 1, User.where(email: " CRUISE-control@EXAMPLE.COM").count
assert_equal user, User.find_by(email: " CRUISE-control@EXAMPLE.COM")
end
def test_reading_old_data
raw_user = RawUser.create!(email: " CRUISE-CONTROL@EXAMPLE.COM\n", alive: 't')
assert_equal " CRUISE-CONTROL@EXAMPLE.COM\n", raw_user.email
assert_equal "t", raw_user.alive
user = User.find(raw_user.id)
assert_equal "cruise-control@example.com", user.email
assert user.alive.eql?(true)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment