leifcr-activeuuid 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.hound.yml +3 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.tb.yml +640 -0
  6. data/.rubocop.yml +12 -0
  7. data/.rvmrc +1 -0
  8. data/.travis.yml +52 -0
  9. data/Gemfile +6 -0
  10. data/LICENSE.md +19 -0
  11. data/README.mkd +167 -0
  12. data/Rakefile +15 -0
  13. data/activeuuid.gemspec +40 -0
  14. data/gemfiles/Gemfile.rails-4-0 +6 -0
  15. data/gemfiles/Gemfile.rails-4-1 +6 -0
  16. data/gemfiles/Gemfile.rails-4-2 +5 -0
  17. data/gemfiles/Gemfile.rails-5-0 +5 -0
  18. data/gemfiles/Gemfile.rails-5-1 +5 -0
  19. data/gemfiles/Gemfile.rails-head +5 -0
  20. data/lib/activeuuid/patches.rb +226 -0
  21. data/lib/activeuuid/railtie.rb +12 -0
  22. data/lib/activeuuid/uuid.rb +166 -0
  23. data/lib/activeuuid/version.rb +3 -0
  24. data/lib/activeuuid.rb +10 -0
  25. data/spec/fabricators/article_fabricator.rb +4 -0
  26. data/spec/fabricators/uuid_article_fabricator.rb +4 -0
  27. data/spec/fabricators/uuid_article_with_namespace_fabricator.rb +4 -0
  28. data/spec/fabricators/uuid_article_with_natural_key_fabricator.rb +4 -0
  29. data/spec/lib/activerecord_spec.rb +224 -0
  30. data/spec/lib/uuid_mokeypatch_spec.rb +30 -0
  31. data/spec/spec_helper.rb +61 -0
  32. data/spec/support/database.yml +12 -0
  33. data/spec/support/migrate/20111117081813_create_articles.rb +13 -0
  34. data/spec/support/migrate/20120817081813_create_uuid_articles.rb +15 -0
  35. data/spec/support/migrate/20141017204945_add_array_to_articles.rb +8 -0
  36. data/spec/support/models/article.rb +3 -0
  37. data/spec/support/models/uuid_article.rb +3 -0
  38. data/spec/support/models/uuid_article_with_namespace.rb +6 -0
  39. data/spec/support/models/uuid_article_with_natural_key.rb +5 -0
  40. data/spec/support/spec_for_adapter.rb +20 -0
  41. metadata +250 -0
@@ -0,0 +1,166 @@
1
+ require "uuidtools"
2
+
3
+ # monkey-patch Friendly::UUID to serialize UUIDs
4
+ module UUIDTools
5
+ class UUID
6
+ alias_method :id, :raw
7
+
8
+ # duck typing activerecord 3.1 dirty hack )
9
+ def gsub(*); self; end
10
+
11
+ def ==(another_uuid)
12
+ to_s == another_uuid.to_s
13
+ end
14
+
15
+ def next
16
+ self.class.random_create
17
+ end
18
+
19
+ def quoted_id
20
+ s = raw.unpack("H*")[0]
21
+ "x'#{s}'"
22
+ end
23
+
24
+ def as_json(_options = nil)
25
+ to_s
26
+ end
27
+
28
+ def to_param
29
+ to_s
30
+ end
31
+
32
+ def self.serialize(value)
33
+ case value
34
+ when self
35
+ value
36
+ when String
37
+ parse_string value
38
+ end
39
+ end
40
+
41
+ def bytesize
42
+ 16
43
+ end
44
+
45
+ private
46
+
47
+ def self.parse_string(str)
48
+ return nil if str.empty?
49
+ if str.length == 36
50
+ parse str
51
+ elsif str.length == 32
52
+ parse_hexdigest str
53
+ else
54
+ parse_raw str
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ module Arel
61
+ module Visitors
62
+ class DepthFirst < Arel::Visitors::Visitor
63
+ def visit_UUIDTools_UUID(o, _a = nil)
64
+ o.quoted_id
65
+ end
66
+ end
67
+
68
+ class MySQL < Arel::Visitors::ToSql
69
+ def visit_UUIDTools_UUID(o, _a = nil)
70
+ o.quoted_id
71
+ end
72
+ end
73
+
74
+ class WhereSql < Arel::Visitors::ToSql
75
+ def visit_UUIDTools_UUID(o)
76
+ o.quoted_id
77
+ end
78
+ end
79
+
80
+ class SQLite < Arel::Visitors::ToSql
81
+ def visit_UUIDTools_UUID(o, _a = nil)
82
+ o.quoted_id
83
+ end
84
+ end
85
+
86
+ class PostgreSQL < Arel::Visitors::ToSql
87
+ def visit_UUIDTools_UUID(o, _a = nil)
88
+ "'#{o}'"
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ module ActiveUUID
95
+ module UUID
96
+ extend ActiveSupport::Concern
97
+
98
+ included do
99
+ class_attribute :_natural_key, instance_writer: false
100
+ class_attribute :_uuid_namespace, instance_writer: false
101
+ class_attribute :_uuid_generator, instance_writer: false
102
+ self._uuid_generator = :random
103
+
104
+ singleton_class.prepend Instantiation
105
+ before_create :generate_uuids_if_needed
106
+ end
107
+
108
+ module ClassMethods
109
+ def natural_key(*attributes)
110
+ self._natural_key = attributes
111
+ end
112
+
113
+ def uuid_namespace(namespace)
114
+ namespace = UUIDTools::UUID.parse_string(namespace) unless namespace.is_a? UUIDTools::UUID
115
+ self._uuid_namespace = namespace
116
+ end
117
+
118
+ def uuid_generator(generator_name)
119
+ self._uuid_generator = generator_name
120
+ end
121
+
122
+ def uuids(*_attributes)
123
+ ActiveSupport::Deprecation.warn <<-EOS
124
+ ActiveUUID detects uuid columns independently.
125
+ There is no more need to use uuid method.
126
+ EOS
127
+ end
128
+
129
+ def uuid_columns
130
+ @uuid_columns ||= columns.select { |c| c.type == :uuid }.map(&:name)
131
+ end
132
+ end
133
+
134
+ module Instantiation
135
+ def instantiate(record, _record_models = nil)
136
+ uuid_columns.each do |uuid_column|
137
+ record[uuid_column] = UUIDTools::UUID.serialize(record[uuid_column]).to_s if record[uuid_column]
138
+ end
139
+
140
+ super(record)
141
+ end
142
+ end
143
+
144
+ def create_uuid
145
+ if _natural_key
146
+ # TODO if all the attributes return nil you might want to warn about this
147
+ chained = _natural_key.map { |attribute| send(attribute) }.join("-")
148
+ UUIDTools::UUID.sha1_create(_uuid_namespace || UUIDTools::UUID_OID_NAMESPACE, chained)
149
+ else
150
+ case _uuid_generator
151
+ when :random
152
+ UUIDTools::UUID.random_create
153
+ when :time
154
+ UUIDTools::UUID.timestamp_create
155
+ end
156
+ end
157
+ end
158
+
159
+ def generate_uuids_if_needed
160
+ primary_key = self.class.primary_key
161
+ if self.class.columns_hash[primary_key].type == :uuid
162
+ send("#{primary_key}=", create_uuid) unless send("#{primary_key}?")
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,3 @@
1
+ module Activeuuid
2
+ VERSION = "0.6.1".freeze
3
+ end
data/lib/activeuuid.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "activeuuid/version"
2
+ require "activeuuid/patches"
3
+ require "activeuuid/uuid"
4
+ require "activeuuid/railtie" if defined?(Rails::Railtie)
5
+ require "pp"
6
+
7
+ module ActiveUUID
8
+ end
9
+
10
+ ActiveUUID::Patches.apply!
@@ -0,0 +1,4 @@
1
+ Fabricator(:article) do
2
+ title { Forgery::LoremIpsum.word }
3
+ body { Forgery::LoremIpsum.sentence }
4
+ end
@@ -0,0 +1,4 @@
1
+ Fabricator(:uuid_article) do
2
+ title { Forgery::LoremIpsum.word }
3
+ body { Forgery::LoremIpsum.sentence }
4
+ end
@@ -0,0 +1,4 @@
1
+ Fabricator(:uuid_article_with_namespace) do
2
+ title { Forgery::LoremIpsum.word }
3
+ body { Forgery::LoremIpsum.sentence }
4
+ end
@@ -0,0 +1,4 @@
1
+ Fabricator(:uuid_article_with_natural_key) do
2
+ title { Forgery::LoremIpsum.word }
3
+ body { Forgery::LoremIpsum.sentence }
4
+ end
@@ -0,0 +1,224 @@
1
+ require "spec_helper"
2
+
3
+ describe ActiveRecord::Base do
4
+ context ".connection" do
5
+ def table_exists?(connection, table_name)
6
+ connection.respond_to?(:data_source_exists?) ?
7
+ connection.data_source_exists?(table_name) :
8
+ connection.table_exists?(table_name)
9
+ end
10
+
11
+ let!(:connection) { ActiveRecord::Base.connection }
12
+ let(:table_name) { :test_uuid_field_creation }
13
+
14
+ before do
15
+ connection.drop_table(table_name) if table_exists?(connection, table_name)
16
+ connection.create_table(table_name)
17
+ end
18
+
19
+ after do
20
+ connection.drop_table table_name
21
+ end
22
+
23
+ specify { expect(table_exists?(connection, table_name)).to be_truthy }
24
+
25
+ context "#add_column" do
26
+ let(:column_name) { :uuid_column }
27
+ let(:column) { connection.columns(table_name).detect { |c| c.name.to_sym == column_name } }
28
+
29
+ before { connection.add_column table_name, column_name, :uuid }
30
+
31
+ specify { expect(connection.column_exists?(table_name, column_name)).to be_truthy }
32
+ specify { expect(column).not_to be_nil }
33
+
34
+ it "should have proper sql type" do
35
+ spec_for_adapter do |adapters|
36
+ adapters.sqlite3 { expect(column.sql_type).to eq("binary(16)") }
37
+ adapters.mysql2 { expect(column.sql_type).to eq("binary(16)") }
38
+ adapters.postgresql { expect(column.sql_type).to eq("uuid") }
39
+ end
40
+ end
41
+ end
42
+
43
+ context "#change_column" do
44
+ let(:column_name) { :string_col }
45
+ let(:column) { connection.columns(table_name).detect { |c| c.name.to_sym == column_name } }
46
+
47
+ before do
48
+ connection.add_column table_name, column_name, :string
49
+ spec_for_adapter do |adapters|
50
+ adapters.sqlite3 { connection.change_column table_name, column_name, :uuid }
51
+ adapters.mysql2 { connection.change_column table_name, column_name, :uuid }
52
+ end
53
+ end
54
+
55
+ it "support changing type from string to uuid" do
56
+ spec_for_adapter do |adapters|
57
+ adapters.sqlite3 { expect(column.sql_type).to eq("binary(16)") }
58
+ adapters.mysql2 { expect(column.sql_type).to eq("binary(16)") }
59
+ adapters.postgresql { skip("postgresql can`t change column type to uuid") }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ describe Article do
67
+ let!(:article) { Fabricate :article }
68
+ let(:id) { article.id }
69
+ let(:model) { Article }
70
+ subject { model }
71
+
72
+ context "model" do
73
+ its(:all) { should == [article] }
74
+ its(:first) { should == article }
75
+ end
76
+
77
+ context "existance" do
78
+ subject { article }
79
+ its(:id) { should be_a Integer }
80
+ end
81
+
82
+ context ".find" do
83
+ specify { expect(model.find(id)).to eq(article) }
84
+ end
85
+
86
+ context ".where" do
87
+ specify { expect(model.where(id: id).first).to eq(article) }
88
+ end
89
+
90
+ context "#destroy" do
91
+ subject { article }
92
+ its(:delete) { should be_truthy }
93
+ its(:destroy) { should be_truthy }
94
+ end
95
+
96
+ context "#save" do
97
+ subject { article }
98
+ let(:array) { [1, 2, 3] }
99
+
100
+ its(:save) { should be_truthy }
101
+
102
+ context "when change array field" do
103
+ before { article.some_array = array }
104
+ its(:save) { should be_truthy }
105
+ end
106
+ end
107
+ end
108
+
109
+ describe UuidArticle do
110
+ let!(:article) { Fabricate :uuid_article }
111
+ let!(:id) { article.id }
112
+ let(:model) { UuidArticle }
113
+ subject { model }
114
+
115
+ context "model" do
116
+ its(:primary_key) { should == "id" }
117
+ its(:all) { should == [article] }
118
+ its(:first) { should == article }
119
+ end
120
+
121
+ context "existance" do
122
+ subject { article }
123
+ its(:id) { should be_a UUIDTools::UUID }
124
+ end
125
+
126
+ context "interpolation" do
127
+ specify { model.where("id = :id", id: article.id) }
128
+ end
129
+
130
+ context "batch interpolation" do
131
+ before { model.update_all(["title = CASE WHEN id = :id THEN 'Passed' ELSE 'Nothing' END", id: article.id]) }
132
+ specify { expect(article.reload.title).to eq("Passed") }
133
+ end
134
+
135
+ context ".find" do
136
+ specify { expect(model.find(article.id)).to eq(article) }
137
+ specify { expect(model.find(id)).to eq(article) }
138
+ specify { expect(model.find(id.to_s)).to eq(article) }
139
+ specify { expect(model.find(id.raw)).to eq(article) }
140
+ end
141
+
142
+ context ".where" do
143
+ specify { expect(model.where(id: article).first).to eq(article) }
144
+ specify { expect(model.where(id: id).first).to eq(article) }
145
+ specify { expect(model.where(id: id.to_s).first).to eq(article) }
146
+ specify { expect(model.where(id: id.raw).first).to eq(article) }
147
+ end
148
+
149
+ context "#destroy" do
150
+ subject { article }
151
+ its(:delete) { should be_truthy }
152
+ its(:destroy) { should be_truthy }
153
+ end
154
+
155
+ context "#reload" do
156
+ subject { article }
157
+ its(:'reload.id') { should == id }
158
+ specify { expect(subject.reload(select: :another_uuid).id).to eq(id) }
159
+ end
160
+
161
+ context "columns" do
162
+ %i[id another_uuid].each do |column|
163
+ context column do
164
+ subject { model.columns_hash[column.to_s] }
165
+ its(:type) { should == :uuid }
166
+ end
167
+ end
168
+ end
169
+
170
+ context "typecasting" do
171
+ let(:uuid) { UUIDTools::UUID.random_create }
172
+ let(:string) { uuid.to_s }
173
+ context "primary" do
174
+ before { article.id = string }
175
+ specify do
176
+ expect(article.id).to eq(uuid)
177
+ expect(article.id_before_type_cast).to eq(string)
178
+ end
179
+ specify do
180
+ expect(article.id_before_type_cast).to eq(string)
181
+ expect(article.id).to eq(uuid)
182
+ end
183
+ end
184
+
185
+ context "non-primary" do
186
+ before { article.another_uuid = string }
187
+ specify do
188
+ expect(article.another_uuid).to eq(uuid)
189
+ expect(article.another_uuid_before_type_cast).to eq(string)
190
+ end
191
+ specify do
192
+ expect(article.another_uuid_before_type_cast).to eq(string)
193
+ expect(article.another_uuid).to eq(uuid)
194
+ end
195
+ specify do
196
+ article.save
197
+ article.reload
198
+ expect(article.another_uuid_before_type_cast).to eq(string)
199
+ expect(article.another_uuid).to eq(uuid)
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ describe UuidArticleWithNaturalKey do
206
+ let!(:article) { Fabricate :uuid_article_with_natural_key }
207
+ let!(:id) { article.id }
208
+ let!(:uuid) { UUIDTools::UUID.sha1_create(UUIDTools::UUID_OID_NAMESPACE, article.title) }
209
+ subject { article }
210
+ context "natural_key" do
211
+ its(:id) { should == uuid }
212
+ end
213
+ end
214
+
215
+ describe UuidArticleWithNamespace do
216
+ let!(:article) { Fabricate :uuid_article_with_namespace }
217
+ let!(:id) { article.id }
218
+ let!(:namespace) { UuidArticleWithNamespace._uuid_namespace }
219
+ let!(:uuid) { UUIDTools::UUID.sha1_create(namespace, article.title) }
220
+ subject { article }
221
+ context "natural_key_with_namespace" do
222
+ its(:id) { should == uuid }
223
+ end
224
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe UUIDTools::UUID do
4
+ let(:input) { "e4618518-cb9f-11e1-aa7c-14dae903e06a" }
5
+ let(:hex) { "E4618518CB9F11E1AA7C14DAE903E06A" }
6
+ let(:uuid) { described_class.parse input }
7
+
8
+ context "instance methods" do
9
+ subject { uuid }
10
+ let(:sql_out) { "x'e4618518cb9f11e1aa7c14dae903e06a'" }
11
+
12
+ its(:quoted_id) { should == sql_out }
13
+ its(:as_json) { should == uuid.to_s }
14
+ its(:to_param) { should == uuid.to_s }
15
+ its(:next) { should be_a(described_class) }
16
+ end
17
+
18
+ describe ".serialize" do
19
+ subject { described_class }
20
+ let(:raw) { uuid.raw }
21
+
22
+ specify { expect(subject.serialize(uuid)).to eq(uuid) }
23
+ specify { expect(subject.serialize(input)).to eq(uuid) }
24
+ specify { expect(subject.serialize(hex)).to eq(uuid) }
25
+ specify { expect(subject.serialize(raw)).to eq(uuid) }
26
+ specify { expect(subject.serialize(nil)).to be_nil }
27
+ specify { expect(subject.serialize("")).to be_nil }
28
+ specify { expect(subject.serialize(5)).to be_nil }
29
+ end
30
+ end
@@ -0,0 +1,61 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ Bundler.require :development
5
+
6
+ require "active_record"
7
+ require "active_support/all"
8
+
9
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
10
+ ActiveRecord::Base.configurations = YAML::safe_load(File.read(File.dirname(__FILE__) + "/support/database.yml"))
11
+
12
+ require "activeuuid"
13
+
14
+ ActiveRecord::Base.establish_connection((ENV["DB"] || "sqlite3").to_sym)
15
+
16
+ if ENV["DB"] == "mysql"
17
+ if ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR <= 1
18
+ class ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
19
+ NATIVE_DATABASE_TYPES[:primary_key] = "int(11) auto_increment PRIMARY KEY"
20
+ end
21
+ elsif ActiveRecord::VERSION::MAJOR == 3
22
+ class ActiveRecord::ConnectionAdapters::Mysql2Adapter
23
+ NATIVE_DATABASE_TYPES[:primary_key] = "int(11) auto_increment PRIMARY KEY"
24
+ end
25
+ end
26
+ end
27
+
28
+ ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + "/support/migrate")
29
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, STDOUT)
30
+
31
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
32
+ Dir["#{File.dirname(__FILE__)}/fabricators/**/*.rb"].each { |f| require f }
33
+
34
+ RSpec.configure do |config|
35
+ # Remove this line if you don't want RSpec's should and should_not
36
+ # methods or matchers
37
+ require "rspec/expectations"
38
+ config.include RSpec::Matchers
39
+
40
+ # == Mock Framework
41
+ config.mock_with :rspec
42
+
43
+ config.before(:suite) do
44
+ DatabaseCleaner.clean_with(:truncation)
45
+ DatabaseCleaner.strategy = :transaction
46
+ end
47
+
48
+ config.before(:each) do
49
+ DatabaseCleaner.start
50
+ end
51
+
52
+ config.after(:each) do
53
+ DatabaseCleaner.clean
54
+ end
55
+
56
+ def spec_for_adapter
57
+ switcher = ActiveUUID::SpecSupport::SpecForAdapter.new
58
+ yield switcher
59
+ switcher.run(connection)
60
+ end
61
+ end
@@ -0,0 +1,12 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+ postgresql:
5
+ adapter: postgresql
6
+ host: localhost
7
+ database: activeuuid_test
8
+ mysql:
9
+ adapter: mysql2
10
+ username: root
11
+ database: activeuuid_test
12
+ encoding: utf8
@@ -0,0 +1,13 @@
1
+ parent_class = ActiveRecord::Migration.respond_to?(:[]) ?
2
+ ActiveRecord::Migration[4.2] :
3
+ ActiveRecord::Migration
4
+ class CreateArticles < parent_class
5
+ def change
6
+ create_table :articles do |t|
7
+ t.string :title
8
+ t.text :body
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ parent_class = ActiveRecord::Migration.respond_to?(:[]) ?
2
+ ActiveRecord::Migration[4.2] :
3
+ ActiveRecord::Migration
4
+ class CreateUuidArticles < parent_class
5
+ def change
6
+ create_table :uuid_articles, id: false do |t|
7
+ t.uuid :id, primary_key: true
8
+ t.string :title
9
+ t.text :body
10
+ t.uuid :another_uuid
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ parent_class = ActiveRecord::Migration.respond_to?(:[]) ?
2
+ ActiveRecord::Migration[4.2] :
3
+ ActiveRecord::Migration
4
+ class AddArrayToArticles < parent_class
5
+ def change
6
+ add_column :articles, :some_array, :integer, array: true
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ class Article < ActiveRecord::Base
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ class UuidArticle < ActiveRecord::Base
2
+ include ActiveUUID::UUID
3
+ end
@@ -0,0 +1,6 @@
1
+ class UuidArticleWithNamespace < ActiveRecord::Base
2
+ include ActiveUUID::UUID
3
+ self.table_name = "uuid_articles"
4
+ natural_key :title
5
+ uuid_namespace "45e676ea-8a43-4ffe-98ca-c142b0062a83" # a random UUID
6
+ end
@@ -0,0 +1,5 @@
1
+ class UuidArticleWithNaturalKey < ActiveRecord::Base
2
+ include ActiveUUID::UUID
3
+ self.table_name = "uuid_articles"
4
+ natural_key :title
5
+ end
@@ -0,0 +1,20 @@
1
+ require "activeuuid"
2
+
3
+ module ActiveUUID::SpecSupport
4
+ class SpecForAdapter
5
+ def initialize
6
+ @specs = {}
7
+ end
8
+
9
+ %i[sqlite3 mysql2 postgresql].each do |name|
10
+ send :define_method, name do |&block|
11
+ @specs[name] = block
12
+ end
13
+ end
14
+
15
+ def run(connection)
16
+ name = connection.adapter_name.downcase.to_sym
17
+ @specs[name].call if @specs.include? name
18
+ end
19
+ end
20
+ end