activeschema 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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