Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: standardtoaster/rails-uuid
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: dojo4/rails-uuid
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Jun 8, 2011

  1. Copy the full SHA
    2915cff View commit details

Commits on Jun 17, 2011

  1. Copy the full SHA
    70c6c0c View commit details
Showing with 223 additions and 26 deletions.
  1. +17 −7 README.md
  2. +0 −1 lib/init.rb
  3. +47 −8 lib/rails-uuid/rails-uuid.rb
  4. +159 −10 lib/rails-uuid/railtie.rb
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
This plugin changes the default handling of primary keys in rails. It currently supports sqlite, sqlite3 and postgre sql.
This plugin changes the default handling of primary keys in rails. It
currently supports sqlite, sqlite3 and postgre sql.

By default, new migrations will use a UUID as the primary key. Should you already have tables, they will still use auto incrementing IDs.
By default, new migrations will use a UUID as the primary key. Should you
already have tables, they will still use auto incrementing IDs.

You can disable the use of UUIDs for a specific table by calling create_table_without_uuid instead of create_table in your migration.
You can disable the use of UUIDs for a specific table by calling
create_table_without_uuid instead of create_table in your migration.

In your migrations, calls to t.references and t.belongs_to should automatically figure out if the destination model is setup for UUIDs. If the model does not already exist (and have a valid table in the DB) it will default to an auto incrementing integer. You can force a traditional relationship by calling t.references_without_uuid. The ability to force a UUID in the references call is coming soon.
In your migrations, calls to t.references and t.belongs_to should
automatically figure out if the destination model is setup for UUIDs. If the
model does not already exist (and have a valid table in the DB) it will
default to an auto incrementing integer. You can force a traditional
relationship by calling t.references_without_uuid. The ability to force a UUID
in the references call is coming soon.

This is a fairly new library and has not been tested heavily, but seems to work with rails 3.0.3.
This is a fairly new library and has not been tested heavily, but seems to
work with rails 3.0.3.

There are no working tests - I've still got to write some. testing framework has been largely stolen from ActiveRecord.
There are no working tests - I've still got to write some. testing framework
has been largely stolen from ActiveRecord.

The gems in the repo are currently out of date.

Original gem template taken from https://github.com/wycats/newgem-template
Original gem template taken from https://github.com/wycats/newgem-template
1 change: 0 additions & 1 deletion lib/init.rb

This file was deleted.

55 changes: 47 additions & 8 deletions lib/rails-uuid/rails-uuid.rb
Original file line number Diff line number Diff line change
@@ -15,55 +15,87 @@ module RailsUUID
'postgresql' => 'primary key',
'mysql' => 'DEFAULT NULL PRIMARY KEY'
}
#TODO: Cache this

## TODO: Cache this
#
def db_type
Rails.configuration.database_configuration[Rails.env]['adapter']
end

#TODO: add t.uuid compatible method
## TODO: add t.uuid compatible method
#
module TableDefinitionUUID
def self.included(base)
base.class_eval do
alias_method_chain :references, :uuid
alias_method_chain(:references, :uuid)
alias :belongs_to :references_with_uuid
end
end
#TODO: Add the ability to force UUID.

## TODO: Add the ability to force UUID.
#
def references_with_uuid(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
force_uuid = options.delete(:uuid)

args.each do |col|
pk_type = :uuid
#copy from column implementation somehow
if !force_uuid && Object.const_defined?(col.to_s.camelize) && !col.to_s.camelize.constantize.pk_is_uuid?
pk_type = :integer
pk_type = :integer
end
column("#{col}_id", pk_type, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
column("#{ col }_id", pk_type, options)
column("#{ col }_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
end
end

def uuid(*args)
options = args.extract_options!
column_names = args
column_names.each { |name| column(name, 'uuid', options) }
column_names.each{|name| column(name, 'uuid', options) }
end
end




module AdapterUUID
def self.included(base)
base.class_eval do
alias_method_chain :native_database_types, :uuid
#alias_method_chain :create_table, :uuid
end
end

def native_database_types_with_uuid
#use native_database_types_without_uuid to get the list - some adapters use a constant, some don't.
#dup() to unfreeze (postgresql freezes primary_key key in the hash)
native_database_types_without_uuid.dup.tap do |h|
h.delete(:primary_key)

h[:uuid] = UUID_DB_PK_TYPE[db_type]
h[:primary_key] = "#{UUID_DB_PK_TYPE[db_type]} #{UUID_DB_PK[db_type]}"
h[:primary_key_autoincrement] = h[:primary_key]
end
=begin
uuid_native_database_types = native_database_types_without_uuid.dup
uuid_native_database_types[:uuid] = UUID_DB_PK_TYPE[db_type]
uuid_native_database_types[:primary_key_autoincrement] = uuid_native_database_types[:primary_key]
uuid_native_database_types[:primary_key] = "#{UUID_DB_PK_TYPE[db_type]} #{UUID_DB_PK[db_type]}"
uuid_native_database_types
=end
end


=begin
def create_table_with_uuid(table_name, options = {})
options.to_options!
options[:id] = false
create_table_without_uuid(table_name, options) do |table_def|
table_def.column 'id', :primary_key_autoincrement
yield table_def if block_given?
end
end
def create_table_without_uuid(table_name, options = {})
options[:id] = false
@@ -72,23 +104,30 @@ def create_table_without_uuid(table_name, options = {})
yield table_def if block_given?
end
end
=end

def db_type
Rails.configuration.database_configuration[Rails.env]['adapter']
end
end




module ActiveRecordUUID
def self.included(base)
base.extend ClassMethods
base.class_eval do
before_create :set_id_as_new_uuid
end
end

module ClassMethods
def pk_is_uuid?
self.columns_hash[self.primary_key].type != :integer
end
end

def set_id_as_new_uuid
if self.class.pk_is_uuid?
self.id ||= UUIDTools::UUID.timestamp_create.to_s
169 changes: 159 additions & 10 deletions lib/rails-uuid/railtie.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,189 @@
module RailsUUID
module RailsUUID
class Railtie < Rails::Railtie
##
# the adapter is not loaded until a connection is actually established, so
# we can't mangle NATIVE_DATABASE_TYPES until the end of initialization The
# values from it should be read DURING the migarion process, so it should
# still work.
#

##
# Rails.application.config.active_record.schema_format = :sql
#

if defined?(Rake)
extend Rake::DSL if defined?(Rake::DSL)

module_eval <<-__, __FILE__, __LINE__
namespace(:db) do
namespace(:schema) do
task(:dump => :environment) do
end
end
end
__
end

config.after_initialize do
## grab the spec

##
# grab the database configuration
#
spec = Rails.configuration.database_configuration[Rails.env].to_options!
adapter = spec[:adapter]
before = ActiveRecord::ConnectionAdapters::AbstractAdapter.subclasses

raise("DB Adapter is not supported by rails-uuid gem") unless UUID_DB_PK_TYPE.has_key?(adapter)

## force the adapter to load, tracking loaded connection adapters
##
# force the adapter to load, tracking loaded connection adapters
#
before_adapter_classes = ActiveRecord::ConnectionAdapters::AbstractAdapter.subclasses
before_column_classes = ActiveRecord::ConnectionAdapters::Column.subclasses

begin
require "active_record/connection_adapters/#{ adapter }_adapter"
rescue LoadError => e
raise "Please install the #{ adapter } adapter: `gem install activerecord-#{ adapter }-adapter` (#{e})"
end
after = ActiveRecord::ConnectionAdapters::AbstractAdapter.subclasses

## now we know the connection class!
after_adapter_classes = ActiveRecord::ConnectionAdapters::AbstractAdapter.subclasses
after_column_classes = ActiveRecord::ConnectionAdapters::Column.subclasses

##
# determine the specific classes we are going to hack the shit out of
#
connection_class =
(before - after).last ||
adapter_class =
(before_adapter_classes - after_adapter_classes).last ||
ActiveRecord::ConnectionAdapters::AbstractAdapter.subclasses.last

## monkey patch the shit out of it... # TODO
column_class =
(before_column_classes - after_column_classes).last ||
ActiveRecord::ConnectionAdapters::Column.subclasses.last


##
# mixin the monkey patches into the right places
#
ActiveRecord::Base.send(:include, RailsUUID::ActiveRecordUUID)

ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, RailsUUID::TableDefinitionUUID)
connection_class.send(:include, RailsUUID::AdapterUUID)

adapter_class.send(:include, RailsUUID::AdapterUUID)

##
# hack the shit out of the adapter to be uuid aware for queries (find) and
# ddl (rake/migrations) actions
#
adapter_class.module_eval do

##
# handle quoting uuids. hack an invalid uuid to force ar to throw
# record_not_found
#
alias_method('__quote__', 'quote') unless method_defined?('__quote__')
def quote(value, column = nil)
return super unless column

if column.type == :uuid && !value.blank?
re = /^ [0-9a-zA-Z]{8} - [0-9a-zA-Z]{4} - [0-9a-zA-Z]{4} - [0-9a-zA-Z]{4} - [0-9a-zA-Z]{12} $/iox
unless value.to_s =~ re
value = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
end
return __quote__(value.to_s)
end

return __quote__(value, column)
end

##
# these *required* for db:schema:dump and friends to work. ar needs to
# *know* about uuids as primary key
#
alias_method('__pk_and_sequence_for__', 'pk_and_sequence_for')
def pk_and_sequence_for(table)
result = __pk_and_sequence_for__(table)

begin
if result.nil? or result == [nil, nil]
if columns(table).detect{|c| c.name.to_s == 'id' and c.type.to_s == 'uuid' }
['id', nil]
else
[nil, nil]
end
else
result
end
rescue Object
abort($!.to_s)
end
end

alias_method('__primary_key__', 'primary_key')
def primary_key(table)
result = __primary_key__(table)

begin
if result.nil? or result == [nil, nil]
if columns(table).detect{|c| c.name.to_s == 'id' and c.type.to_s == 'uuid' }
'id'
else
nil
end
else
result
end
rescue Object
abort($!.to_s)
end
end
end


column_class.module_eval do
def simplified_type_with_uuid(field_type)
field_type.to_s == 'uuid' ? :uuid : simplified_type_without_uuid(field_type)
end
alias_method_chain(:simplified_type, :uuid)


=begin
def type_cast_with_uuid(value)
raise value.inspect
return nil if value.nil?
case type
when :string
case sql_type.to_s
when /uuid/
raise value.inspect
#raise(({:value => value, :type => type, :sql_type => sql_type}.inspect))
else
value.to_s
end
else
type_cast_without_uuid(value)
end
end
alias_method_chain(:type_cast, :uuid)
=end

=begin
def type_cast_with_uuid(value)
return nil if value.nil?
case type
when :string
case value
else
value.to_s
end
else
type_cast_without_uuid(value)
end
end
alias_method_chain(:type_cast, :uuid)
=end
end
end

end