communard 0.0.5 → 0.1.0
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 +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
|