Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Invalid Values to be Converted to nil (optional) #46

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,60 @@ Migrating shouldn't be that difficult though. `UUIDTools::UUID` implements
by default. But it's good to be aware of this in case you're running into
weirdness.

## Invalid Values

By default, this will raise an exception if an invalid value is attempted to be stored.
However, the PostgreSQL driver nils invalid UUID values.
To get PG-friendly behavior, pass in the option `:nil_invalid_values => true` when creating the attribute.

Additionally, as a convenience function, you can add the following to `ApplicationRecord` to get parity between PG and MySQL:

```
def self.uuid_fields(*fields)
if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?("mysql")
fields.each do |fld|
attribute fld, MySQLBinUUID::Type.new(:nil_invalid_values => true)
end
end
end
```

Then, in your models, just begin them by marking the fields which are UUIDs.
Comment on lines +136 to +154
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this to as a section under # Usage (after ## Tell your model how to handle..) please? I think we could just describe the options there, but don't need to specifically mention parity between Postgres and MySQL:

## Options
You can pass in the following options to `MySQLBinUUID::Type.new(option: "value")` when
registering the attribute:

  * `nil_invalid_values: true` (default: `false`) - When set to true, invalid UUIDs will be stored
    as `nil` values. When set the false (the default) an `InvalidUUID` error is raised. If you
    don't want `nil` values to be stored you can use a presence validator.


## UUID Type in migrations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to leave this out as it was intentionally not included with this gem.

It's nice of you though to inform people how they could achieve this. I'd propose moving the example code you wrote into a Gist, and link to it in a new final line of the ## No uuid column in database migrations chapter in the README:

If you'd like to have a `uuid` type available in your migrations, take a look at this gist:
---gist-link-goes-here---


To get a UUID type in a migration, add the following to your initializers (Rails 7 specific):

```
require "active_record"
require "active_record/connection_adapters/mysql2_adapter"
require "active_record/type_caster/map"

module ActiveRecord
module ConnectionAdapters
module MySQL
module ColumnMethods
def uuid(*args, **options)
# http://dba.stackexchange.com/questions/904/mysql-data-type-for-128-bit-integers
# http://dev.mysql.com/doc/refman/5.7/en/binary-varbinary.html
args.each { |name| column(name, "varbinary(16)", **options.merge(limit: 16)) }
end
end
module SchemaStatements
def type_to_sql(type, limit: nil, precision: nil, scale: nil, size: limit_to_size(limit, type), unsigned: nil, **)
if type.to_s == "uuid"
return "varbinary(16)"
else
super
end
end
end
end
end
end

ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::NATIVE_DATABASE_TYPES[:uuid] = {:name => "varbinary", :limit => 16}
```

# Known issues
* With Rails 5.0 in combination with uniqueness validations, ActiveRecord
Expand Down
7 changes: 7 additions & 0 deletions lib/mysql-binuuid/type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ module MySQLBinUUID
class InvalidUUID < StandardError; end

class Type < ActiveModel::Type::Binary
attr_accessor :options

def initialize(opts = {})
self.options = opts
end
Comment on lines +5 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like to have default options available, also as an opportunity to see which options there are and what their default values are.

Suggested change
attr_accessor :options
def initialize(opts = {})
self.options = opts
end
attr_reader :options
DEFAULT_OPTIONS = {
# By default an +InvalidUUID+ error is raised when a UUID is invalid. If you want to get a
# nil value instead, register the attribute with `nil_invalid_values: true`.
nil_invalid_values: false
}.freeze
def initialize(opts = {})
@options = DEFAULT_OPTIONS.merge(opts)
end


def type
:uuid
end
Expand Down Expand Up @@ -34,6 +40,7 @@ def serialize(value)
# does not explicity escape the Binary data type. escaping is implicit as
# the Binary data type always converts its value to a hex string.
unless valid_undashed_uuid?(undashed_uuid)
return nil if options[:nil_invalid_values]
raise MySQLBinUUID::InvalidUUID, "#{value} is not a valid UUID"
end

Expand Down