communard 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +53 -31
- data/bin/communard +44 -16
- data/communard.gemspec +2 -2
- data/lib/communard/commands.rb +156 -0
- data/lib/communard/configuration.rb +72 -27
- data/lib/communard/rake.rb +64 -60
- data/lib/communard/version.rb +3 -1
- data/lib/communard.rb +6 -13
- data/spec/integration_spec.rb +14 -57
- data/spec/spec_helper.rb +3 -13
- metadata +7 -8
- data/lib/communard/context.rb +0 -118
- data/lib/communard/maintenance.rb +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 94f9d91890f50281fc8ce1d4678153cf2aff2c2068c00166774aeeafe80dfb16
|
4
|
+
data.tar.gz: cd21196d14631a2e3eda1435e16c4fd4afdabc1da2ab16d0d198290c1275e153
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4968eb2fb22782695eb7335028f0d8fb76d58a3bb4fe437ec3e3da75fb8b1edc74cc66a8b2fceeabbad8c6c44aa7adf8c206566ba08fed93552ec8fe356521ae
|
7
|
+
data.tar.gz: 55000cbc291e4ca20050c1fff461b709f689aeea3a17f28d922ec8c69220d042dbd22ff628f5be8ce2889594ab9c0d8fa652ac82b9b60c6113d16d3e278e7101
|
data/README.md
CHANGED
@@ -2,9 +2,6 @@
|
|
2
2
|
|
3
3
|
Communard adds some conventions from [ActiveRecord][ar] to [Sequel][sq].
|
4
4
|
|
5
|
-
This means you can use `config/database.yml` and `db/migrate` again, so you
|
6
|
-
don't have to change deployment scripts that are made for ActiveRecord.
|
7
|
-
|
8
5
|
Sequel doesn't provide the exact same functionality as ActiveRecord. Communard
|
9
6
|
doesn't try to make Sequel quack like ActiveRecord, it just tries to help with
|
10
7
|
some (not all) setup.
|
@@ -31,25 +28,15 @@ $ gem install communard
|
|
31
28
|
|
32
29
|
## Usage
|
33
30
|
|
34
|
-
### Connecting to the database
|
35
|
-
|
36
|
-
To get a database connection:
|
37
|
-
|
38
|
-
``` ruby
|
39
|
-
DB = Communard.connect
|
40
|
-
```
|
41
|
-
|
42
|
-
The `DB` object will be familiar to you if you've ever read the Sequel documentation.
|
43
|
-
|
44
|
-
Note: Communard doesn't remember your connection.
|
45
|
-
|
46
31
|
### Rake integration
|
47
32
|
|
48
33
|
To add most Rake tasks, add this to your `Rakefile` or to `lib/tasks/communard.rake`:
|
49
34
|
|
50
35
|
``` ruby
|
51
36
|
require "communard/rake"
|
52
|
-
|
37
|
+
namespace :db do
|
38
|
+
Communard::Rake.add_tasks
|
39
|
+
end
|
53
40
|
```
|
54
41
|
|
55
42
|
This will add the most used rake tasks, like `db:create`, `db:migrate`, and `db:setup`.
|
@@ -60,34 +47,68 @@ To see them all:
|
|
60
47
|
$ rake -T db
|
61
48
|
```
|
62
49
|
|
50
|
+
`Communard::Rake.add_tasks` accepts the same configuration options as
|
51
|
+
`Sequel.connect`. It doesn't immediately make a connection, only when needed.
|
52
|
+
|
53
|
+
The default connection string is `ENV["DATABASE_URL"]`, so if you use that,
|
54
|
+
there is no need to configure anything.
|
55
|
+
|
56
|
+
Other configuration options, can be set via a block:
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
namespace :db do
|
60
|
+
Communard::Rake.add_tasks do |config|
|
61
|
+
|
62
|
+
# Change where the application is located, defaults to Dir.pwd
|
63
|
+
config.root_path = Dir.pwd
|
64
|
+
|
65
|
+
# Automatically generate schema (default: false)
|
66
|
+
config.dump_after_migrating = false
|
67
|
+
|
68
|
+
# Dump types in native format (default) or Ruby (more portable)
|
69
|
+
config.same_db = true
|
70
|
+
|
71
|
+
# Add a logger
|
72
|
+
config.logger = Logger.new("log/migrations.log")
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
Example with using `config/database.yml`:
|
79
|
+
|
80
|
+
``` ruby
|
81
|
+
namespace :db do
|
82
|
+
environment = ENV["RAILS_ENV"] || "development"
|
83
|
+
all_config = YAML.load_file("config/database.yml")
|
84
|
+
Communard::Rake.add_tasks(config.fetch(environment))
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Note about test environment: Communard doesn't try to create a test database
|
89
|
+
like ActiveRecord does. The only rake task that attempts to do that is
|
90
|
+
`rake db:test:prepare`. It respawns rake with different environment variables
|
91
|
+
set. Your mileage may vary.
|
92
|
+
|
63
93
|
### Migrations
|
64
94
|
|
65
95
|
To generate a migration:
|
66
96
|
|
67
97
|
```
|
68
|
-
$
|
98
|
+
$ communard migration create_posts
|
69
99
|
```
|
70
100
|
|
71
101
|
Communard doesn't support more arguments, like the Rails generator does. You'll
|
72
102
|
have to edit the generated migration file yourself.
|
73
103
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
to set them.
|
79
|
-
|
80
|
-
``` ruby
|
81
|
-
DB = Communard.connect { |config|
|
82
|
-
config.root = Rails.root
|
83
|
-
config.logger = Rails.logger
|
84
|
-
config.environment = Rails.env.to_s
|
85
|
-
}
|
86
|
-
```
|
104
|
+
Communard supports both timestamps and integer versions. It automatically
|
105
|
+
detects which type you have. If you have no migrations yet and want to use
|
106
|
+
timestamps, add `--timestamps`. Read more about how to choose in the
|
107
|
+
[Sequel docs][tm].
|
87
108
|
|
88
109
|
## Contributing
|
89
110
|
|
90
|
-
1. Fork it ( https://github.com/
|
111
|
+
1. Fork it ( https://github.com/iain/communard/fork )
|
91
112
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
113
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
114
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -95,3 +116,4 @@ DB = Communard.connect { |config|
|
|
95
116
|
|
96
117
|
[ar]: http://rubyonrails.org
|
97
118
|
[sq]: http://sequel.jeremyevans.net
|
119
|
+
[tm]: http://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html#label-How+to+choose
|
data/bin/communard
CHANGED
@@ -1,26 +1,54 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
require "optparse"
|
3
|
+
command = ARGV.shift
|
5
4
|
|
6
|
-
|
5
|
+
case command
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
when nil, "-h", "--help", "help"
|
8
|
+
puts "Usage: communard migration NAME"
|
9
|
+
exit
|
10
|
+
|
11
|
+
when "-v", "--version"
|
12
|
+
require "communard"
|
13
|
+
puts Communard::VERSION
|
14
|
+
exit
|
15
|
+
|
16
|
+
when "migration", "-m", "--generate-migration", "m"
|
17
|
+
migration_name = ARGV.shift
|
18
|
+
|
19
|
+
require "optparse"
|
20
|
+
require "pathname"
|
21
|
+
require "fileutils"
|
22
|
+
|
23
|
+
migrations_dir = Pathname(Dir.pwd).join("db/migrate")
|
24
|
+
FileUtils.mkdir_p(migrations_dir)
|
25
|
+
|
26
|
+
migration_files = Pathname.glob(migrations_dir.join("*.rb")).map { |f| f.basename.to_s }
|
12
27
|
|
13
|
-
|
14
|
-
|
15
|
-
exit
|
28
|
+
if (conflict = migration_files.find { |f| f.match?(/\A\d+_#{migration_name}.rb\z/) })
|
29
|
+
abort "Migration with same name already exists: #{conflict}"
|
16
30
|
end
|
17
31
|
|
18
|
-
|
19
|
-
|
20
|
-
|
32
|
+
options = {}
|
33
|
+
|
34
|
+
OptionParser.new do |opts|
|
35
|
+
opts.on "--[no-]timestamps", "Use timestamps for versions" do |bool|
|
36
|
+
options[:use_timestamps] = bool
|
37
|
+
end
|
38
|
+
end.parse!
|
39
|
+
|
40
|
+
versions = migration_files.map { |file| file.to_s.split("_", 2).first.to_i }
|
41
|
+
|
42
|
+
if !options.has_key?(:use_timestamps)
|
43
|
+
options[:use_timestamps] = versions.any? { |v| v > 20000101 }
|
21
44
|
end
|
22
45
|
|
23
|
-
|
24
|
-
|
46
|
+
version = options[:use_timestamps] ? Time.now.strftime("%Y%m%d%H%M%S") : "%03d" % (versions.max.to_i + 1)
|
47
|
+
|
48
|
+
filename = migrations_dir.join("#{version}_#{migration_name}.rb")
|
25
49
|
|
26
|
-
puts
|
50
|
+
File.open(filename, "w") { |f| f.puts "Sequel.migration do\n\n change do\n end\n\nend" }
|
51
|
+
puts "Migration generated: #{filename.relative_path_from(Pathname(Dir.pwd))}"
|
52
|
+
else
|
53
|
+
abort "Unknown command: #{command}"
|
54
|
+
end
|
data/communard.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["iain@iain.nl"]
|
11
11
|
spec.summary = %q{Adds some conventions from ActiveRecord to Sequel.}
|
12
12
|
spec.description = %q{Adds some conventions from ActiveRecord to Sequel.}
|
13
|
-
spec.homepage = "https://github.com/
|
13
|
+
spec.homepage = "https://github.com/iain/communard"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "sequel", "~>
|
21
|
+
spec.add_dependency "sequel", "~> 5.2"
|
22
22
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.6"
|
24
24
|
spec.add_development_dependency "rake"
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Communard
|
4
|
+
class Commands
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :configuration
|
9
|
+
|
10
|
+
def_delegators :configuration,
|
11
|
+
:connection,
|
12
|
+
:adapter,
|
13
|
+
:database_name,
|
14
|
+
:options,
|
15
|
+
:root_path
|
16
|
+
|
17
|
+
def initialize(configuration)
|
18
|
+
@configuration = configuration
|
19
|
+
Sequel.extension :migration, :core_extensions
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_database
|
23
|
+
return if adapter == "sqlite"
|
24
|
+
run_without_database("CREATE DATABASE %{database_name}")
|
25
|
+
rescue Sequel::DatabaseError => error
|
26
|
+
if error.message.to_s =~ /database (.*) already exists/
|
27
|
+
configuration.default_logger.warn "Database #{$1} already exists."
|
28
|
+
else
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def drop_database
|
34
|
+
if adapter == "sqlite"
|
35
|
+
file = database_name
|
36
|
+
File.rm(file) if File.exist?(file)
|
37
|
+
else
|
38
|
+
run_without_database("DROP DATABASE IF EXISTS %{database_name}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def migrate(target: nil)
|
43
|
+
target = Integer(target) if target
|
44
|
+
migrator(target: target, current: nil).run
|
45
|
+
dump_schema if target.nil? && configuration.dump_after_migrating
|
46
|
+
end
|
47
|
+
|
48
|
+
def seed
|
49
|
+
load seeds_file if seeds_file.exist?
|
50
|
+
end
|
51
|
+
|
52
|
+
def rollback(step: 1)
|
53
|
+
target = applied_migrations[-step - 1]
|
54
|
+
if target
|
55
|
+
migrate(target: target.split(/_/, 2).first)
|
56
|
+
else
|
57
|
+
fail ArgumentError, "Cannot roll back to #{step}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_schema
|
62
|
+
migration = instance_eval(schema_file.read, schema_file.expand_path.to_s, 1)
|
63
|
+
conn = configuration.silent_connection
|
64
|
+
migration.apply(conn, :up)
|
65
|
+
end
|
66
|
+
|
67
|
+
def dump_schema
|
68
|
+
conn = configuration.silent_connection
|
69
|
+
conn.extension :schema_dumper
|
70
|
+
schema = conn.dump_schema_migration(same_db: configuration.same_db)
|
71
|
+
schema_file.open("w") { |f| f.puts schema.gsub(/^\s+$/m, "").gsub(/:(\w+)=>/, '\1: ') }
|
72
|
+
end
|
73
|
+
|
74
|
+
def status
|
75
|
+
results = Hash.new { |h, k| h[k] = Status.new(k, false, false) }
|
76
|
+
available = Pathname.glob(migrations_dir.join("*.rb")).map(&:basename).map(&:to_s)
|
77
|
+
available.each { |migration| results[migration].available = true }
|
78
|
+
applied_migrations(available).each { |migration| results[migration].applied = true }
|
79
|
+
|
80
|
+
$stdout.puts
|
81
|
+
$stdout.puts "database: #{connection.opts.fetch(:database)}"
|
82
|
+
$stdout.puts
|
83
|
+
$stdout.puts " Status Migration ID Migration Name"
|
84
|
+
$stdout.puts "--------------------------------------------------"
|
85
|
+
results.values.sort.each do |result|
|
86
|
+
$stdout.puts " %-7s %-15s %s" % [ result.status, result.id, result.name ]
|
87
|
+
end
|
88
|
+
$stdout.puts
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_without_database(query)
|
92
|
+
opts = options.dup
|
93
|
+
database_name = opts.delete("database")
|
94
|
+
if adapter == "postgres"
|
95
|
+
opts["database"] = "postgres"
|
96
|
+
opts["schema_search_path"] = "public"
|
97
|
+
end
|
98
|
+
conn = Sequel.connect(opts)
|
99
|
+
conn.run(query % { database_name: database_name })
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def applied_migrations(available)
|
105
|
+
m = migrator(allow_missing_migration_files: true)
|
106
|
+
if m.is_a?(Sequel::IntegerMigrator)
|
107
|
+
available.select { |f| f.split("_", 2).first.to_i <= m.current }
|
108
|
+
else
|
109
|
+
instance.applied_migrations
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def migrator(opts = {})
|
114
|
+
migrator = Sequel::Migrator.migrator_class(migrations_dir)
|
115
|
+
migrator.new(connection, migrations_dir, opts)
|
116
|
+
end
|
117
|
+
|
118
|
+
def schema_file
|
119
|
+
root_path.join("db/schema.rb")
|
120
|
+
end
|
121
|
+
|
122
|
+
def seeds_file
|
123
|
+
root_path.join("db/seeds.rb")
|
124
|
+
end
|
125
|
+
|
126
|
+
def migrations_dir
|
127
|
+
root_path.join("db/migrate")
|
128
|
+
end
|
129
|
+
|
130
|
+
Status = Struct.new(:file, :applied, :available) do
|
131
|
+
|
132
|
+
def <=>(other)
|
133
|
+
id <=> other.id
|
134
|
+
end
|
135
|
+
|
136
|
+
def status
|
137
|
+
return "????" unless available
|
138
|
+
applied ? " up " : "down"
|
139
|
+
end
|
140
|
+
|
141
|
+
def id
|
142
|
+
splitted.first
|
143
|
+
end
|
144
|
+
|
145
|
+
def name
|
146
|
+
splitted.last.capitalize.tr("_", " ").sub(/\.rb$/, "")
|
147
|
+
end
|
148
|
+
|
149
|
+
def splitted
|
150
|
+
file.split(/_/, 2)
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
@@ -1,52 +1,97 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "logger"
|
3
|
+
require "uri"
|
4
|
+
|
1
5
|
module Communard
|
2
6
|
class Configuration
|
3
7
|
|
4
|
-
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
def initialize(conn_string = ENV["DATABASE_URL"], opts = Sequel::OPTS)
|
11
|
+
@conn_string = conn_string
|
12
|
+
@opts = opts
|
13
|
+
|
14
|
+
case conn_string
|
15
|
+
when String
|
16
|
+
uri = URI.parse(conn_string)
|
17
|
+
@options = {
|
18
|
+
"adapter" => uri.scheme,
|
19
|
+
"user" => uri.user,
|
20
|
+
"password" => uri.password,
|
21
|
+
"port" => uri.port,
|
22
|
+
"host" => uri.hostname,
|
23
|
+
"database" => (m = %r{/(.*)}.match(uri.path)) && (m[1]),
|
24
|
+
}
|
25
|
+
when Hash
|
26
|
+
@options = conn_string.map { |k, v| [ k.to_s, v ] }.to_h
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Sequel::Database.connect takes either a Hash or a String, given: #{conn_string.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
self.root_path = Dir.pwd
|
32
|
+
self.logger = nil
|
33
|
+
self.dump_after_migrating = true
|
34
|
+
self.same_db = true
|
5
35
|
|
6
|
-
def initialize
|
7
|
-
self.environment = ENV["RACK_ENV"] || ENV["RUBY_ENV"] || ENV["RACK_ENV"] || "development"
|
8
|
-
self.root = Pathname(Dir.pwd)
|
9
|
-
self.logger = stdout_logger
|
10
|
-
self.log_level = :info
|
11
|
-
self.dump_same_db = false
|
12
|
-
self.sql_log_level = :debug
|
13
|
-
self.log_warn_duration = 0.5
|
14
36
|
yield self if block_given?
|
15
37
|
end
|
16
38
|
|
17
|
-
|
18
|
-
|
19
|
-
|
39
|
+
attr_accessor :logger
|
40
|
+
|
41
|
+
attr_accessor :same_db
|
42
|
+
|
43
|
+
attr_accessor :dump_after_migrating
|
20
44
|
|
21
|
-
|
22
|
-
|
45
|
+
attr_reader :root_path
|
46
|
+
|
47
|
+
def root_path=(path)
|
48
|
+
@root_path = Pathname(path)
|
23
49
|
end
|
24
50
|
|
25
|
-
def
|
26
|
-
|
51
|
+
def connection
|
52
|
+
Sequel.connect(@conn_string, @opts).tap { |c|
|
53
|
+
c.loggers = [logger, default_logger].compact
|
54
|
+
c.sql_log_level = :debug
|
55
|
+
}
|
27
56
|
end
|
28
57
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
58
|
+
def silent_connection
|
59
|
+
Sequel.connect(@conn_string, @opts).tap { |c|
|
60
|
+
c.loggers = [logger].compact
|
61
|
+
}
|
34
62
|
end
|
35
63
|
|
36
|
-
def
|
64
|
+
def default_logger(out = $stdout)
|
37
65
|
::Logger.new(out).tap { |l|
|
38
66
|
alternate = 0
|
39
67
|
l.formatter = Proc.new { |sev, _, _, msg|
|
40
68
|
alternate = ((alternate + 1) % 2)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
69
|
+
msg = if sev == "DEBUG"
|
70
|
+
" #{msg}"
|
71
|
+
else
|
72
|
+
"[#{sev}] #{msg}"
|
73
|
+
end
|
74
|
+
if out.tty?
|
75
|
+
color = case sev
|
76
|
+
when "INFO" then 35 + alternate
|
77
|
+
when "DEBUG" then 30
|
78
|
+
else 31
|
79
|
+
end
|
80
|
+
"\e[#{color}m#{msg}\e[0m\n"
|
81
|
+
else
|
82
|
+
"#{msg}\n"
|
45
83
|
end
|
46
|
-
"\e[#{color}m[#{sev}]\e[0m #{msg}\n"
|
47
84
|
}
|
48
85
|
}
|
49
86
|
end
|
50
87
|
|
88
|
+
def adapter
|
89
|
+
options.fetch("adapter")
|
90
|
+
end
|
91
|
+
|
92
|
+
def database_name
|
93
|
+
options.fetch("database")
|
94
|
+
end
|
95
|
+
|
51
96
|
end
|
52
97
|
end
|
data/lib/communard/rake.rb
CHANGED
@@ -5,83 +5,87 @@ module Communard
|
|
5
5
|
|
6
6
|
extend ::Rake::DSL if defined?(::Rake::DSL)
|
7
7
|
|
8
|
-
def self.add_tasks(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@_communard_context = Communard.context(&block)
|
13
|
-
end
|
8
|
+
def self.add_tasks(*args, &block)
|
9
|
+
task :_load_communard do
|
10
|
+
@_communard_commands = Communard.commands(*args, &block)
|
11
|
+
end
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
desc "Creates the database, migrate the schema, and loads seed data"
|
14
|
+
task setup: ["db:create", "db:migrate", "db:seed", "db:test:prepare"]
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
desc "Creates the database"
|
17
|
+
task create: :_load_communard do
|
18
|
+
@_communard_commands.create_database
|
19
|
+
end
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
desc "Drops the database"
|
22
|
+
task drop: :_load_communard do
|
23
|
+
@_communard_commands.drop_database
|
24
|
+
end
|
27
25
|
|
28
|
-
|
29
|
-
|
26
|
+
desc "Drops and creates the database"
|
27
|
+
task reset: ["db:drop", "db:setup"]
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
desc "Migrate the database"
|
30
|
+
task migrate: :_load_communard do
|
31
|
+
target = ENV["TARGET"]
|
32
|
+
@_communard_commands.migrate(target: target)
|
33
|
+
end
|
36
34
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
desc "Load the seed data from db/seeds.rb"
|
36
|
+
task seed: :_load_communard do
|
37
|
+
@_communard_commands.seed
|
38
|
+
end
|
41
39
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
40
|
+
desc "Rolls the schema back to the previous version"
|
41
|
+
task rollback: :_load_communard do
|
42
|
+
step = Integer(ENV["STEP"] || 1)
|
43
|
+
@_communard_commands.rollback(step: step)
|
44
|
+
end
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
46
|
+
namespace :test do
|
47
|
+
desc "Cleans the test database"
|
48
|
+
task prepare: :_load_communard do
|
49
|
+
env = {
|
50
|
+
"RACK_ENV" => "test",
|
51
|
+
"RAILS_ENV" => "test",
|
52
|
+
"RUBY_ENV" => "test",
|
53
|
+
"DATABASE_URL" => nil,
|
54
|
+
}
|
55
|
+
Process.spawn(env, $PROGRAM_NAME, "db:drop", "db:create", "db:schema:load")
|
56
|
+
_pid, status = Process.wait2
|
57
|
+
fail "Failed to re-create test database" if status.exitstatus != 0
|
56
58
|
end
|
59
|
+
end
|
57
60
|
|
58
|
-
|
61
|
+
namespace :migrate do
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
desc "Redo the last migration"
|
64
|
+
task redo: :_load_communard do
|
65
|
+
commands = @_communard_commands
|
66
|
+
commands.rollback
|
67
|
+
commands.migrate
|
68
|
+
end
|
66
69
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
70
|
+
desc "Display status of migrations"
|
71
|
+
task status: :_load_communard do
|
72
|
+
@_communard_commands.status
|
71
73
|
end
|
72
74
|
|
73
|
-
|
75
|
+
desc "Drop and recreate database with migrations"
|
76
|
+
task reset: ["db:drop", "db:create", "db:migrate"]
|
77
|
+
end
|
74
78
|
|
75
|
-
|
76
|
-
task :load => :_load_communard do
|
77
|
-
@_communard_context.load_schema
|
78
|
-
end
|
79
|
+
namespace :schema do
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
desc "Load the schema from db/schema.rb"
|
82
|
+
task load: :_load_communard do
|
83
|
+
@_communard_commands.load_schema
|
84
|
+
end
|
84
85
|
|
86
|
+
desc "Dumps the schema to db/schema.rb"
|
87
|
+
task dump: :_load_communard do
|
88
|
+
@_communard_commands.dump_schema
|
85
89
|
end
|
86
90
|
|
87
91
|
end
|
data/lib/communard/version.rb
CHANGED
data/lib/communard.rb
CHANGED
@@ -1,24 +1,17 @@
|
|
1
|
-
require "sequel"
|
2
|
-
require "logger"
|
3
|
-
require "pathname"
|
1
|
+
require "sequel/core"
|
4
2
|
|
5
3
|
require "communard/version"
|
6
|
-
require "communard/maintenance"
|
7
4
|
require "communard/configuration"
|
8
|
-
require "communard/
|
5
|
+
require "communard/commands"
|
9
6
|
|
10
7
|
module Communard
|
11
8
|
|
12
|
-
def self.
|
13
|
-
|
9
|
+
def self.commands(*args, &block)
|
10
|
+
Commands.new(configuration(*args, &block))
|
14
11
|
end
|
15
12
|
|
16
|
-
def self.
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.configuration(&block)
|
21
|
-
Configuration.new(&block)
|
13
|
+
def self.configuration(*args, &block)
|
14
|
+
Configuration.new(*args, &block)
|
22
15
|
end
|
23
16
|
|
24
17
|
end
|
data/spec/integration_spec.rb
CHANGED
@@ -3,57 +3,34 @@ require "yaml"
|
|
3
3
|
RSpec.describe "Integration", type: :aruba do
|
4
4
|
|
5
5
|
example "SQLite" do
|
6
|
-
run_tests(
|
7
|
-
"adapter" => "sqlite",
|
8
|
-
"database" => "db/test.sqlite3",
|
9
|
-
"pool" => 5,
|
10
|
-
"timeout" => 5000,
|
11
|
-
)
|
6
|
+
run_tests("sqlite://db/test.sqlite3")
|
12
7
|
end
|
13
8
|
|
14
9
|
example "PostgreSQL" do
|
15
|
-
run_tests(
|
16
|
-
"adapter" => "postgres",
|
17
|
-
"database" => "communard_test",
|
18
|
-
"pool" => 5,
|
19
|
-
"timeout" => 5000,
|
20
|
-
)
|
10
|
+
run_tests("postgresql://localhost:5432/communard_test")
|
21
11
|
end
|
22
12
|
|
23
13
|
example "MySQL" do
|
24
|
-
run_tests(
|
25
|
-
"adapter" => "mysql2",
|
26
|
-
"database" => "communard_test",
|
27
|
-
"username" => "root",
|
28
|
-
"pool" => 5,
|
29
|
-
"timeout" => 5000,
|
30
|
-
)
|
14
|
+
run_tests("mysql2://root@localhost:3306/communard_test")
|
31
15
|
end
|
32
16
|
|
33
|
-
|
34
17
|
def run_tests(database_config)
|
35
|
-
|
36
|
-
write_file "config/database.yml", { "development" => database_config }.to_yaml
|
37
|
-
|
38
18
|
write_file "Rakefile", <<-FILE.gsub(/^\s{6}/, "")
|
39
19
|
$LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
|
40
|
-
require "yaml"
|
41
20
|
require "communard/rake"
|
42
|
-
|
21
|
+
namespace :db do
|
22
|
+
Communard::Rake.add_tasks("#{database_config}")
|
23
|
+
end
|
43
24
|
FILE
|
44
25
|
|
45
|
-
run_simple "bundle exec communard
|
26
|
+
run_simple "bundle exec communard migration create_posts"
|
46
27
|
|
47
|
-
|
28
|
+
glob = Dir[expand_path("db/migrate/*_create_posts.rb")]
|
29
|
+
file = glob.first
|
48
30
|
|
49
|
-
expect(File.read(file)).to eq
|
50
|
-
Sequel.migration do
|
51
|
-
change do
|
52
|
-
end
|
53
|
-
end
|
54
|
-
FILE
|
31
|
+
expect(File.read(file)).to eq "Sequel.migration do\n\n change do\n end\n\nend\n"
|
55
32
|
|
56
|
-
write_file file, <<-FILE.gsub(/^\s{6}/, "")
|
33
|
+
write_file "db/migrate/#{File.basename(file)}", <<-FILE.gsub(/^\s{6}/, "")
|
57
34
|
Sequel.migration do
|
58
35
|
change do
|
59
36
|
create_table :posts do
|
@@ -64,31 +41,11 @@ RSpec.describe "Integration", type: :aruba do
|
|
64
41
|
end
|
65
42
|
FILE
|
66
43
|
|
67
|
-
run_simple "rake db:drop"
|
68
|
-
|
69
|
-
run_simple "rake db:create"
|
44
|
+
run_simple "bundle exec rake db:drop"
|
70
45
|
|
71
|
-
run_simple "rake db:
|
46
|
+
run_simple "bundle exec rake db:create"
|
72
47
|
|
73
|
-
|
74
|
-
$LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
|
75
|
-
require "yaml"
|
76
|
-
require "communard"
|
77
|
-
|
78
|
-
db = Communard.connect
|
79
|
-
posts = db[:posts]
|
80
|
-
|
81
|
-
4.times do
|
82
|
-
posts.insert(name: "hello world")
|
83
|
-
end
|
84
|
-
|
85
|
-
puts "Post count: '\#{posts.count}'"
|
86
|
-
FILE
|
87
|
-
|
88
|
-
run_simple "ruby app.rb"
|
89
|
-
|
90
|
-
assert_partial_output("Post count: '4'", all_stdout)
|
48
|
+
run_simple "bundle exec rake db:migrate"
|
91
49
|
end
|
92
50
|
|
93
|
-
|
94
51
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,22 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require 'aruba/reporting'
|
1
|
+
require "aruba"
|
3
2
|
|
4
3
|
RSpec.configure do |config|
|
5
4
|
config.disable_monkey_patching!
|
6
5
|
config.include Aruba::Api, type: :aruba
|
7
6
|
|
8
|
-
config.before :each do
|
9
|
-
next unless self.class.include?(Aruba::Api)
|
7
|
+
config.before :each, type: :aruba do
|
10
8
|
restore_env
|
11
|
-
|
12
|
-
|
13
|
-
if ENV["DEBUG"] == "true"
|
14
|
-
@announce_stdout = true
|
15
|
-
@announce_stderr = true
|
16
|
-
@announce_cmd = true
|
17
|
-
@announce_dir = true
|
18
|
-
@announce_env = true
|
19
|
-
end
|
9
|
+
setup_aruba
|
20
10
|
end
|
21
11
|
|
22
12
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: communard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iain
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,14 +139,13 @@ files:
|
|
139
139
|
- bin/communard
|
140
140
|
- communard.gemspec
|
141
141
|
- lib/communard.rb
|
142
|
+
- lib/communard/commands.rb
|
142
143
|
- lib/communard/configuration.rb
|
143
|
-
- lib/communard/context.rb
|
144
|
-
- lib/communard/maintenance.rb
|
145
144
|
- lib/communard/rake.rb
|
146
145
|
- lib/communard/version.rb
|
147
146
|
- spec/integration_spec.rb
|
148
147
|
- spec/spec_helper.rb
|
149
|
-
homepage: https://github.com/
|
148
|
+
homepage: https://github.com/iain/communard
|
150
149
|
licenses:
|
151
150
|
- MIT
|
152
151
|
metadata: {}
|
@@ -166,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
165
|
version: '0'
|
167
166
|
requirements: []
|
168
167
|
rubyforge_project:
|
169
|
-
rubygems_version: 2.
|
168
|
+
rubygems_version: 2.7.1
|
170
169
|
signing_key:
|
171
170
|
specification_version: 4
|
172
171
|
summary: Adds some conventions from ActiveRecord to Sequel.
|
data/lib/communard/context.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
module Communard
|
2
|
-
class Context
|
3
|
-
|
4
|
-
attr_reader :configuration
|
5
|
-
|
6
|
-
def initialize(configuration)
|
7
|
-
@configuration = configuration
|
8
|
-
end
|
9
|
-
|
10
|
-
def connect(opts = options)
|
11
|
-
::Sequel.connect(opts).tap do |connection|
|
12
|
-
connection.loggers = loggers
|
13
|
-
connection.sql_log_level = configuration.sql_log_level
|
14
|
-
connection.log_warn_duration = configuration.log_warn_duration
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def generate_migration(name: nil)
|
19
|
-
fail ArgumentError, "Name is required" if name.to_s == ""
|
20
|
-
require "fileutils"
|
21
|
-
underscore = name.
|
22
|
-
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
23
|
-
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
24
|
-
tr("-", "_").
|
25
|
-
downcase
|
26
|
-
filename = root.join("db/migrate/#{Time.now.strftime("%Y%m%d%H%M%S")}_#{underscore}.rb")
|
27
|
-
FileUtils.mkdir_p(File.dirname(filename))
|
28
|
-
File.open(filename, "w") { |f| f << "Sequel.migration do\n change do\n end\nend" }
|
29
|
-
puts "#{filename} created"
|
30
|
-
end
|
31
|
-
|
32
|
-
def create_database(env: environment)
|
33
|
-
unless adapter(env: env) == "sqlite"
|
34
|
-
run_without_database("CREATE DATABASE %{database_name}", env: env)
|
35
|
-
end
|
36
|
-
rescue Sequel::DatabaseError => error
|
37
|
-
if /database (.*) already exists/ === error.message
|
38
|
-
loggers.each { |logger| logger.info "Database #{$1} already exists, which is fine." }
|
39
|
-
else
|
40
|
-
raise
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def drop_database(env: environment)
|
45
|
-
fail ArgumentError, "Don't drop the production database, you monkey!" if env.to_s == "production"
|
46
|
-
if adapter(env: env) == "sqlite"
|
47
|
-
file = options(env: env).fetch("database")
|
48
|
-
if File.exist?(file)
|
49
|
-
File.rm(file)
|
50
|
-
end
|
51
|
-
else
|
52
|
-
run_without_database("DROP DATABASE IF EXISTS %{database_name}", env: env)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def migrate(target: nil, env: environment)
|
57
|
-
maintenance(env: env).migrate(target: target, dump_same_db: configuration.dump_same_db)
|
58
|
-
end
|
59
|
-
|
60
|
-
def seed(env: environment)
|
61
|
-
maintenance(env: env).seed
|
62
|
-
end
|
63
|
-
|
64
|
-
def rollback(step: 1, env: environment)
|
65
|
-
maintenance(env: env).rollback(step: step, dump_same_db: configuration.dump_same_db)
|
66
|
-
end
|
67
|
-
|
68
|
-
def load_schema(env: environment)
|
69
|
-
maintenance(env: env).load_schema
|
70
|
-
end
|
71
|
-
|
72
|
-
def dump_schema(env: environment)
|
73
|
-
maintenance(env: env).dump_schema(dump_same_db: configuration.dump_same_db)
|
74
|
-
end
|
75
|
-
|
76
|
-
def status(env: environment)
|
77
|
-
maintenance(env: env).status
|
78
|
-
end
|
79
|
-
|
80
|
-
def run_without_database(cmd, env: environment)
|
81
|
-
opts = options(env: env).dup
|
82
|
-
database_name = opts.delete("database")
|
83
|
-
if opts.fetch("adapter") == "postgres"
|
84
|
-
opts["database"] = "postgres"
|
85
|
-
opts["schema_search_path"] = "public"
|
86
|
-
end
|
87
|
-
connection = connect(opts)
|
88
|
-
connection.run(cmd % { database_name: database_name })
|
89
|
-
end
|
90
|
-
|
91
|
-
def options(env: environment)
|
92
|
-
YAML.load_file(root.join("config/database.yml")).fetch(env.to_s)
|
93
|
-
end
|
94
|
-
|
95
|
-
private
|
96
|
-
|
97
|
-
def maintenance(env: environment)
|
98
|
-
Maintenance.new(connection: connect(options(env: env)), root: root)
|
99
|
-
end
|
100
|
-
|
101
|
-
def environment
|
102
|
-
configuration.environment
|
103
|
-
end
|
104
|
-
|
105
|
-
def root
|
106
|
-
configuration.root
|
107
|
-
end
|
108
|
-
|
109
|
-
def loggers
|
110
|
-
configuration.loggers
|
111
|
-
end
|
112
|
-
|
113
|
-
def adapter(env: environment)
|
114
|
-
options(env: env).fetch("adapter").to_s
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
118
|
-
end
|
@@ -1,102 +0,0 @@
|
|
1
|
-
module Communard
|
2
|
-
class Maintenance
|
3
|
-
|
4
|
-
attr_reader :connection, :root
|
5
|
-
|
6
|
-
def initialize(connection: nil, root: nil)
|
7
|
-
::Sequel.extension :migration, :core_extensions
|
8
|
-
@connection = connection
|
9
|
-
@root = root
|
10
|
-
end
|
11
|
-
|
12
|
-
def migrate(target: nil, dump_same_db: false)
|
13
|
-
target = Integer(target) if target
|
14
|
-
::Sequel::Migrator.run(connection, migrations, target: target, allow_missing_migration_files: true)
|
15
|
-
dump_schema(dump_same_db: dump_same_db)
|
16
|
-
end
|
17
|
-
|
18
|
-
def seed
|
19
|
-
load seeds_file if seeds_file.exist?
|
20
|
-
end
|
21
|
-
|
22
|
-
def rollback(step: 1, dump_same_db: false)
|
23
|
-
target = applied_migrations[-step - 1]
|
24
|
-
if target
|
25
|
-
migrate(target: target.split(/_/, 2).first, dump_same_db: dump_same_db)
|
26
|
-
else
|
27
|
-
fail ArgumentError, "Cannot roll back that far"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def load_schema
|
32
|
-
migration = instance_eval(schema_file.read, schema_file.expand_path.to_s, 1)
|
33
|
-
migration.apply(connection, :up)
|
34
|
-
end
|
35
|
-
|
36
|
-
def dump_schema(dump_same_db: false)
|
37
|
-
connection.extension :schema_dumper
|
38
|
-
schema = connection.dump_schema_migration(same_db: dump_same_db)
|
39
|
-
schema_file.open("w") { |f| f << schema.gsub(/\s+$/m, "") }
|
40
|
-
end
|
41
|
-
|
42
|
-
def status
|
43
|
-
results = Hash.new { |h,k| h[k] = Status.new(k, false, false) }
|
44
|
-
available = Pathname.glob(migrations.join("*.rb")).map(&:basename).map(&:to_s).reverse
|
45
|
-
available.each { |migration| results[migration].available = true }
|
46
|
-
applied_migrations.each { |migration| results[migration].applied = true }
|
47
|
-
|
48
|
-
puts
|
49
|
-
puts "database: #{connection.opts.fetch(:database)}"
|
50
|
-
puts
|
51
|
-
puts " Status Migration ID Migration Name"
|
52
|
-
puts "--------------------------------------------------"
|
53
|
-
results.values.sort.each do |result|
|
54
|
-
puts " #{result.status} #{result.id} #{result.name}"
|
55
|
-
end
|
56
|
-
puts
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def applied_migrations
|
62
|
-
::Sequel::Migrator.migrator_class(migrations).new(connection, migrations, allow_missing_migration_files: true).applied_migrations
|
63
|
-
end
|
64
|
-
|
65
|
-
def schema_file
|
66
|
-
root.join("db/schema.rb")
|
67
|
-
end
|
68
|
-
|
69
|
-
def seeds_file
|
70
|
-
root.join("db/seeds.rb")
|
71
|
-
end
|
72
|
-
|
73
|
-
def migrations
|
74
|
-
root.join("db/migrate")
|
75
|
-
end
|
76
|
-
|
77
|
-
Status = Struct.new(:file, :applied, :available) do
|
78
|
-
|
79
|
-
def <=>(other)
|
80
|
-
id <=> other.id
|
81
|
-
end
|
82
|
-
|
83
|
-
def status
|
84
|
-
available ? applied ? " up " : "down" : "????"
|
85
|
-
end
|
86
|
-
|
87
|
-
def id
|
88
|
-
splitted.first
|
89
|
-
end
|
90
|
-
|
91
|
-
def name
|
92
|
-
splitted.last.capitalize.gsub("_", " ").sub(/\.rb$/, "")
|
93
|
-
end
|
94
|
-
|
95
|
-
def splitted
|
96
|
-
file.split(/_/, 2)
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
end
|
102
|
-
end
|