activeschema 0.0.0 → 0.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d34a9132fca30741b7068eb347fd44e48bd03370
4
- data.tar.gz: 8cd3965d1c97c86bd4ed705486f6f898ae134cf7
3
+ metadata.gz: 079547f5a035f7e3a866f1f1cfe5adc2da584a65
4
+ data.tar.gz: aab407a26b4bf56e80b064172375bb0631a0f58f
5
5
  SHA512:
6
- metadata.gz: f29cf2886d2b5397acca430b1614f0946e5423ed0f0648d604f8a844d6abb6ec121d995052add560b7d374ded6445c0b8e95ad4084ecc125d097a07e8801674b
7
- data.tar.gz: eeb27abdffb683d785f13d44e2975ccc1466e73b63d72aec62a30187c64440126b178f651772825b8c61c833938b892f86c1e74ae9e2aca468b1943daa6415cc
6
+ metadata.gz: bde07fdb4962b00451965f0de9706c3e5936c2deea66143d424dd12108d8e693a478a1312672b3b4b1b754ddc38c17256a5020810b57ce1ce8383fb8ce29a1c5
7
+ data.tar.gz: e6e72aa0d897cd7f123e27f857fc813c88ab249ac7e8b2d59edccce50e09dc98cc0fcecbe93450cdeff8abd367d0854c87d17c6858c06f6f9941ed5b18cba42e
data/.gitignore CHANGED
File without changes
data/.pryrc ADDED
@@ -0,0 +1,7 @@
1
+ require 'continuation'
2
+ require 'active_record'
3
+ $:.unshift(File.expand_path('../lib', __FILE__))
4
+ require 'active_schema'
5
+
6
+ ActiveRecord::Base.establish_connection('postgres://dev@localhost/dev')
7
+ connection = ActiveRecord::Base.connection
data/Gemfile CHANGED
File without changes
data/LICENSE.txt CHANGED
File without changes
data/README.md CHANGED
File without changes
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+
3
+ task :test do
4
+ require './spec/run'
5
+ end
6
+
7
+ task default: :test
data/activeschema.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- name, version = 'activeschema 0.0.0'.split
3
+ name, version = 'activeschema 0.0.1'.split
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = name
6
6
  spec.version = version
@@ -14,11 +14,12 @@ Gem::Specification.new do |spec|
14
14
  spec.files = Dir['**/{*,.[a-z]*}'].reject {|e| e =~ /\.(gem|lock)\Z/}
15
15
  spec.require_paths = ['lib']
16
16
 
17
- spec.executables = Dir['bin/*'].map{|f| File.basename(f)}
17
+ spec.required_ruby_version = '>= 2.0'
18
18
 
19
- spec.required_ruby_version = '>= 1.9.2'
19
+ spec.add_dependency 'activerecord', '~> 3.2'
20
20
 
21
- spec.add_development_dependency 'minitest'
21
+ spec.add_development_dependency 'minispec'
22
+ spec.add_development_dependency 'activerecord-nulldb-adapter'
22
23
  spec.add_development_dependency 'bundler'
23
24
  spec.add_development_dependency 'rake'
24
25
  end
@@ -0,0 +1,16 @@
1
+ module ActiveSchema
2
+ module API
3
+
4
+ def self.included base
5
+ base.extend(self)
6
+ end
7
+
8
+ def schema opts = {}, &schema
9
+ opts = Hash[opts] # somehow i do not like #dup in this context...
10
+ table = opts.delete(:table) || self.table_name
11
+ connection = opts.delete(:connection) || ActiveRecord::Base.connection
12
+ ActiveSchema::SCHEMAS[table.to_s] = [connection, opts, schema]
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,65 @@
1
+ module ActiveSchema
2
+ class Column
3
+
4
+ def initialize connection, table_name, column_name, column_type, column_opts
5
+ @connection, @table_name, @column_name, @column_type, @column_opts = \
6
+ connection, table_name, column_name, column_type, column_opts
7
+ return add_column unless column_exists?
8
+ update_column_type_if_needed
9
+ update_column_if_needed
10
+ end
11
+
12
+ private
13
+ def update_column_type_if_needed
14
+ update_column(@column_type) unless column_exists?(@column_type)
15
+ end
16
+
17
+ def update_column_if_needed
18
+ @connection.reset! # reconnect to get fresh columns info
19
+ @column_opts.each_pair do |o,v|
20
+ next if column_exists?(@column_type, o => v)
21
+ update_column(@column_type, o => v)
22
+ end
23
+ end
24
+
25
+ def update_column type, opts = {}
26
+ @connection.change_column(@table_name, @column_name, type, opts)
27
+ end
28
+
29
+ def column_exists? type = nil, opts = {}
30
+ @connection.column_exists?(@table_name, @column_name, type, opts)
31
+ end
32
+
33
+ def add_column
34
+ @connection.add_column(@table_name, @column_name, @column_type, @column_opts)
35
+ end
36
+
37
+ class Pruner
38
+ def initialize connection, table_name, specified_columns
39
+ @connection, @table_name, @specified_columns = \
40
+ connection, table_name, specified_columns.map(&:to_s)
41
+ @connection.reset!
42
+ prune_as_needed
43
+ end
44
+
45
+ private
46
+ def prune_as_needed
47
+ (existing_columns - Array(primary_key) - @specified_columns).each do |column_name|
48
+ remove_column(column_name)
49
+ end
50
+ end
51
+
52
+ def primary_key
53
+ @connection.primary_key(@table_name)
54
+ end
55
+
56
+ def remove_column column_name
57
+ @connection.remove_column(@table_name, column_name)
58
+ end
59
+
60
+ def existing_columns
61
+ @connection.columns(@table_name).map(&:name).map(&:to_s)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveSchema
2
+ class Index
3
+
4
+ def initialize connection, table_name, columns, opts
5
+ @connection, @table_name, @columns, @opts = \
6
+ connection, table_name, columns, opts
7
+
8
+ # nothing to do if index of given columns with given opts already exists
9
+ return if index_exists?(@opts)
10
+
11
+ # if index of given columns exists and it has different options, remove it
12
+ remove_index if index_exists?
13
+
14
+ add_index
15
+ end
16
+
17
+ private
18
+
19
+ def index_exists? opts = {}
20
+ @connection.index_exists?(@table_name, @columns, opts)
21
+ end
22
+
23
+ def add_index
24
+ @connection.add_index(@table_name, @columns, @opts)
25
+ end
26
+
27
+ def remove_index
28
+ # @connection.remove_index(@table_name, column: @columns)
29
+ end
30
+
31
+ class Pruner
32
+
33
+ def initialize connection, table_name, indexes
34
+ @connection, @table_name, @indexes = \
35
+ connection, table_name, indexes
36
+
37
+ existing = @connection.indexes(@table_name).map(&:name)
38
+ specified = @indexes.map {|c| @connection.index_name(@table_name, c)}
39
+
40
+ p [existing, specified]
41
+
42
+ (existing - specified).each do |index|
43
+ @connection.remove_index(@table_name, index)
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,77 @@
1
+ module ActiveSchema
2
+ class Schema
3
+
4
+ def initialize connection, table_name, table_opts, &schema
5
+ @connection, @table_name, @table_opts, @schema = \
6
+ connection, table_name, table_opts, schema
7
+ @strict = @table_opts.keys.map(&:to_sym).include?(:strict) ? @table_opts.delete(:strict) : true
8
+ @columns, @indexes = {}, {}
9
+ yield_schema
10
+ table = Table.new(@connection, @table_name, @table_opts, @columns)
11
+ table.exists? ? handle_columns : table.create
12
+ handle_indexes
13
+ end
14
+
15
+ # by default ActiveSchema will drop db columns that's not defined in schema.
16
+ # so the table will reflect the exact structure of your schema
17
+ # use `strict: false` option to allow columns not defined in schema.
18
+ #
19
+ # @example
20
+ # # => \d users
21
+ # # Table "public.users"
22
+ # # Column | Type
23
+ # # ----------------------+------------------------
24
+ # # id | integer
25
+ # # email | character varying(255)
26
+ # # first_name | character varying(255)
27
+ # # last_name | character varying(255)
28
+ # # username | character varying(255)
29
+ #
30
+ # schema :users, strict: false do |t|
31
+ # # this schema WILL KEEP username column
32
+ # t.string :email
33
+ # t.string :first_name
34
+ # t.string :last_name
35
+ # end
36
+ #
37
+ # schema :users do |t|
38
+ # # this schema WILL DROP username column
39
+ # t.string :email
40
+ # t.string :first_name
41
+ # t.string :last_name
42
+ # end
43
+ #
44
+ def strict?; @strict end
45
+
46
+ def column name, type, opts = {}
47
+ @columns[name.to_s] = [type, opts]
48
+ end
49
+
50
+ def index columns, opts = {}
51
+ @indexes[Array(columns).map(&:to_sym)] = opts
52
+ end
53
+
54
+ def method_missing type, name, opts = {}
55
+ column(name, type, opts)
56
+ end
57
+
58
+ private
59
+ def yield_schema
60
+ @schema.call(self) if @schema
61
+ end
62
+
63
+ def handle_columns
64
+ @columns.each_pair do |column, (type, opts)|
65
+ Column.new(@connection, @table_name, column, type, opts)
66
+ end
67
+ Column::Pruner.new(@connection, @table_name, @columns.keys) if strict?
68
+ end
69
+
70
+ def handle_indexes
71
+ @indexes.each_pair do |columns, opts|
72
+ Index.new(@connection, @table_name, columns, opts)
73
+ end
74
+ Index::Pruner.new(@connection, @table_name, @indexes.keys) if strict?
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveSchema
2
+ class Table
3
+
4
+ def initialize connection, table_name, table_opts, columns
5
+ @connection, @table_name, @table_opts, @columns =
6
+ connection, table_name, table_opts, columns
7
+ end
8
+
9
+ def create
10
+ @connection.create_table(@table_name, @table_opts) do |t|
11
+ @columns.each_pair do |name,(type,opts)|
12
+ t.column(name, type, opts)
13
+ end
14
+ end
15
+ end
16
+
17
+ def exists?
18
+ @connection.table_exists?(@table_name)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveSchema
2
+ SCHEMAS = {}
3
+
4
+ def apply table = nil
5
+ schemas = table ? SCHEMAS.select {|t,_| t == table} : SCHEMAS
6
+ schemas.each_pair do |table, (conn, opts, schema)|
7
+ Schema.new(conn, table, opts, &schema)
8
+ end
9
+ end
10
+ module_function :apply
11
+ end
12
+
13
+ require 'active_schema/api'
14
+ require 'active_schema/schema'
15
+ require 'active_schema/table'
16
+ require 'active_schema/column'
17
+ require 'active_schema/index'
data/lib/activeschema.rb CHANGED
@@ -1,5 +1 @@
1
- require "activeschema/version"
2
-
3
- module Activeschema
4
- # Your code goes here...
5
- end
1
+ require 'active_schema'
@@ -0,0 +1,73 @@
1
+ describe ActiveSchema::Column do
2
+ include_setup :*
3
+
4
+ before do
5
+ model.schema {|t| t.string :name}
6
+ end
7
+
8
+ it 'creates columns that does not exists' do
9
+ mock(connection, table_exists?: true)
10
+
11
+ expect(connection).
12
+ to_receive(:column_exists?).
13
+ with {|*a| a.include?('name')}.
14
+ and_return(false)
15
+
16
+ expect(connection).
17
+ to_receive(:add_column).
18
+ with {|t,c,*| t == model.table_name && c == 'name'}
19
+
20
+ model.apply_schema
21
+ end
22
+
23
+ it 'does not try to create columns that already exists' do
24
+ mock(connection, table_exists?: true, column_exists?: true)
25
+ spy(connection, :add_column)
26
+ model.apply_schema
27
+ assert(connection).did_not.received(:add_column)
28
+ end
29
+
30
+ it 'updates column type' do
31
+ model.apply_schema
32
+ assert(connection.columns(model.table_name).map(&:name)).include?('name')
33
+ model.schema {|t| t.text :name}
34
+
35
+ expect(connection).
36
+ to_receive(:change_column).
37
+ with(model.table_name, 'name', :text, {})
38
+
39
+ model.apply_schema
40
+ end
41
+
42
+ it 'updates column options' do
43
+ model.apply_schema
44
+ column = connection.columns(model.table_name).find {|c| c.name == 'name'}
45
+ assert(column.class.name) =~ /Column/
46
+ is(column.default).nil?
47
+
48
+ model.schema {|t| t.string :name, default: 'blah'}
49
+
50
+ expect(connection).
51
+ to_receive(:change_column).
52
+ with(model.table_name, 'name', :string, {default: 'blah'})
53
+
54
+ model.apply_schema
55
+ end
56
+
57
+ it 'updates both type and options' do
58
+ model.apply_schema
59
+ model.schema {|t| t.text :name, limit: 50}
60
+
61
+ # called once to update type
62
+ expect(connection).
63
+ to_receive(:change_column).
64
+ with(model.table_name, 'name', :text, {})
65
+
66
+ # and once to update options
67
+ expect(connection).
68
+ to_receive(:change_column).
69
+ with(model.table_name, 'name', :text, {limit: 50})
70
+
71
+ model.apply_schema
72
+ end
73
+ end
data/spec/db/schema.rb ADDED
File without changes
@@ -0,0 +1,94 @@
1
+ describe ActiveSchema::Index do
2
+ include_setup :*
3
+
4
+ before do
5
+ model.schema do |t|
6
+ t.string :name
7
+ t.index :name
8
+ end
9
+ end
10
+
11
+ # it 'creates indexes that does not exists' do
12
+ # expect(connection).
13
+ # to_receive(:index_exists?).
14
+ # with(model.table_name, [:name], {})
15
+
16
+ # model.apply_schema
17
+ # end
18
+
19
+ # should 'remove then add an index of given columns if it has different options' do
20
+ # model.apply_schema
21
+
22
+ # expect(connection).
23
+ # to_receive(:index_exists?).
24
+ # with(model.table_name, [:name], {}).
25
+ # and_return(true)
26
+
27
+ # expect(connection).
28
+ # to_receive(:index_exists?).
29
+ # with(model.table_name, [:name], {unique: true}).
30
+ # and_return(false)
31
+
32
+ # expect(connection).
33
+ # to_receive(:remove_index).
34
+ # with(model.table_name, column: [:name])
35
+
36
+ # model.schema {|t| t.index(:name, unique: true)}
37
+
38
+ # expect do
39
+ # # NullDB adapter does not really remove indexes
40
+ # # so ActiveRecord raises an exception when new index with same name added
41
+ # model.apply_schema
42
+ # end.to_raise(ArgumentError, /Index.+already exists/)
43
+ # end
44
+
45
+ # should 'ignore existing indexes' do
46
+ # model.apply_schema
47
+
48
+ # expect(connection).
49
+ # to_receive(:index_exists?).
50
+ # with(model.table_name, [:name], {}).
51
+ # and_return(true)
52
+
53
+ # spy(connection, :remove_index)
54
+
55
+ # model.apply_schema
56
+
57
+ # assert(connection).did_not.received(:remove_index)
58
+ # end
59
+
60
+ context 'strict mode' do
61
+
62
+ before do
63
+ stub(connection, :indexes).
64
+ with(model.table_name) do
65
+ [Struct.new(:name).new('some_manually_added_index')]
66
+ end
67
+ end
68
+
69
+ # should 'keep unspecified indexes on unrestricted mode' do
70
+ # model.schema(strict: false)
71
+
72
+ # spy(connection, :remove_index)
73
+
74
+ # model.apply_schema
75
+
76
+ # assert(connection).did_not.received(:remove_index)
77
+ # end
78
+
79
+ should 'remove unspecified indexes on strict mode' do
80
+ model.schema(strict: true)
81
+
82
+ # stub(connection, :remove_index)
83
+
84
+ expect(connection).
85
+ to_receive(:remove_index).
86
+ with(model.table_name, 'some_manually_added_index').
87
+ with_caller {|c| puts c*"\n"}
88
+
89
+ model.apply_schema
90
+ # @__ms__messages.each {|b| p b.reject {|k,v| k == :object}}
91
+ end
92
+ end
93
+
94
+ end
data/spec/run.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'minispec'
2
+ require 'active_record'
3
+ require 'nulldb'
4
+ $:.unshift(File.expand_path('../../lib', __FILE__))
5
+ require 'active_schema'
6
+
7
+ ActiveRecord::Base.extend(ActiveSchema::API)
8
+ NullDB.configure {|c| def c.project_root; File.expand_path('../', __FILE__) end}
9
+
10
+ module ActiveSchema::API
11
+ # monkey-patch to easily apply schema on models
12
+ def apply_schema
13
+ ActiveSchema.apply(table_name)
14
+ end
15
+ end
16
+
17
+ shared_setup :* do
18
+ before do
19
+ ActiveRecord::Base.establish_connection(adapter: :nulldb)
20
+ stub(connection, :change_column) # needed to avoid NotImplemented exception
21
+ end
22
+
23
+ after do
24
+ ActiveRecord::Base.remove_connection
25
+ end
26
+
27
+ let :model do
28
+ Class.new(ActiveRecord::Base) do
29
+ self.table_name = '_%s_' % self.__id__
30
+ end
31
+ end
32
+
33
+ let(:connection) { ActiveRecord::Base.connection }
34
+ end
35
+
36
+ Minispec.pattern = 'spec/index*'
37
+ require 'minispec/autorun'
@@ -0,0 +1,21 @@
1
+ describe ActiveSchema::Table do
2
+ include_setup :*
3
+
4
+ before do
5
+ model.schema
6
+ end
7
+
8
+ it 'creates tables that does not exists' do
9
+ expect(connection).
10
+ to_receive(:create_table).
11
+ with {|t,*| t == model.table_name}
12
+ model.apply_schema
13
+ end
14
+
15
+ it 'does not try to create tables that already exists' do
16
+ mock(connection, table_exists?: true)
17
+ spy(connection, :create_table)
18
+ model.apply_schema
19
+ assure(connection).did_not.received(:create_table)
20
+ end
21
+ end
metadata CHANGED
@@ -1,17 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeschema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Milton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-30 00:00:00.000000000 Z
11
+ date: 2014-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: minitest
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minispec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activerecord-nulldb-adapter
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
45
  - - ">="
@@ -60,13 +88,24 @@ extensions: []
60
88
  extra_rdoc_files: []
61
89
  files:
62
90
  - ".gitignore"
91
+ - ".pryrc"
63
92
  - Gemfile
64
93
  - LICENSE.txt
65
94
  - README.md
66
95
  - Rakefile
67
96
  - activeschema.gemspec
97
+ - lib/active_schema.rb
98
+ - lib/active_schema/api.rb
99
+ - lib/active_schema/column.rb
100
+ - lib/active_schema/index.rb
101
+ - lib/active_schema/schema.rb
102
+ - lib/active_schema/table.rb
68
103
  - lib/activeschema.rb
69
- - lib/activeschema/version.rb
104
+ - spec/column_spec.rb
105
+ - spec/db/schema.rb
106
+ - spec/index_spec.rb
107
+ - spec/run.rb
108
+ - spec/table_spec.rb
70
109
  homepage: https://github.com/smilton/activeschema
71
110
  licenses:
72
111
  - MIT
@@ -79,7 +118,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
118
  requirements:
80
119
  - - ">="
81
120
  - !ruby/object:Gem::Version
82
- version: 1.9.2
121
+ version: '2.0'
83
122
  required_rubygems_version: !ruby/object:Gem::Requirement
84
123
  requirements:
85
124
  - - ">="
@@ -90,6 +129,6 @@ rubyforge_project:
90
129
  rubygems_version: 2.2.2
91
130
  signing_key:
92
131
  specification_version: 4
93
- summary: activeschema-0.0.0
132
+ summary: activeschema-0.0.1
94
133
  test_files: []
95
134
  has_rdoc:
@@ -1,3 +0,0 @@
1
- module Activeschema
2
- VERSION = "0.0.1"
3
- end