radical 1.0.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -2
- data/CHANGELOG.md +46 -1
- data/Gemfile +4 -2
- data/README.md +5 -1
- data/exe/rad +41 -0
- data/lib/radical/app.rb +61 -27
- data/lib/radical/asset.rb +24 -0
- data/lib/radical/asset_compiler.rb +40 -0
- data/lib/radical/assets.rb +45 -0
- data/lib/radical/controller.rb +54 -11
- data/lib/radical/database.rb +43 -44
- data/lib/radical/env.rb +1 -0
- data/lib/radical/flash.rb +61 -0
- data/lib/radical/form.rb +75 -15
- data/lib/radical/generator/app/.env +5 -0
- data/lib/radical/generator/app/Gemfile +7 -0
- data/lib/radical/generator/app/app.rb +37 -0
- data/lib/radical/generator/app/config.ru +5 -0
- data/lib/radical/generator/app/controllers/controller.rb +4 -0
- data/lib/radical/generator/app/models/model.rb +4 -0
- data/lib/radical/generator/app/routes.rb +5 -0
- data/lib/radical/generator/blank_migration.rb +11 -0
- data/lib/radical/generator/controller.rb +59 -0
- data/lib/radical/generator/migration.rb +13 -0
- data/lib/radical/generator/model.rb +9 -0
- data/lib/radical/generator/views/_form.rb +6 -0
- data/lib/radical/generator/views/edit.rb +3 -0
- data/lib/radical/generator/views/index.rb +24 -0
- data/lib/radical/generator/views/new.rb +4 -0
- data/lib/radical/generator/views/show.rb +5 -0
- data/lib/radical/generator.rb +155 -0
- data/lib/radical/migration.rb +45 -0
- data/lib/radical/model.rb +3 -12
- data/lib/radical/router.rb +143 -42
- data/lib/radical/routes.rb +59 -0
- data/lib/radical/security_headers.rb +27 -0
- data/lib/radical/strings.rb +17 -0
- data/lib/radical/table.rb +2 -0
- data/lib/radical/view.rb +19 -9
- data/lib/radical.rb +11 -0
- data/radical.gemspec +4 -3
- metadata +44 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d14c9a576701105d60a08c6339a19aeebc080b6a4fd6f7235d8c5dec82ea287
|
4
|
+
data.tar.gz: b03f1d945e0f441401d99375062423dff70fa9c50cc394e470444d253faa46c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbdbdd7c8a1a56755ef772bbd7ce24a197111d97d44be0d1c1f6a56be24361d1f80469583f4969c0d8b8d9500ce92a66333e71539e8a79b845f249659b72a6ec
|
7
|
+
data.tar.gz: d3425aef0d2a3d87788b7169ba81b67b8c71faed045227aaff586a699ad46482d0770cfaacf0a738d787bb28b20be42c47b7d88ef79eda5632af5abe91da1e1b
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,50 @@
|
|
1
1
|
# Changelog
|
2
|
+
|
2
3
|
All notable changes to this project will be documented in this file
|
3
4
|
|
4
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
5
6
|
|
6
|
-
|
7
|
+
# [Unreleased]
|
8
|
+
|
9
|
+
# 1.2.0 (2021-12-17)
|
10
|
+
|
11
|
+
- *Breaking* Support only one level of nested resources, make them shallow only
|
12
|
+
- *Breaking* Move `db/migrations` to `migrations`
|
13
|
+
- *Breaking* Change `<%== f.button 'Save' %>` to `<%== f.submit 'Save' %>`
|
14
|
+
- *Breaking* Change the form helpers from `model[name]` to just `name`, it's cleaner.
|
15
|
+
- Add `Radical.env.[development?|production?|test?]`
|
16
|
+
- Add `_path` support for nested resource routes
|
17
|
+
- Add a random secret to the session cookie on startup
|
18
|
+
- Add asset concatention + compression (no minification yet)
|
19
|
+
- Remove dependence on rack-flash3
|
20
|
+
- Add security headers
|
21
|
+
- Add session configuration with `session` method in `App`
|
22
|
+
- Move all `_path` method definitions to `Radical::Controller`
|
23
|
+
- Add `exe/rad`
|
24
|
+
- Add `rad g mvc Todo(s) name:text done_at:integer` generators
|
25
|
+
- Add attrs to form helpers
|
26
|
+
- Add `rad g app` generator
|
27
|
+
- Add migrate/rollback `rad` commands
|
28
|
+
|
29
|
+
# 1.1.0 (2021-12-06)
|
30
|
+
|
31
|
+
- *Breaking* `root`, `resource` and `resources` methods no longer take class constants
|
32
|
+
- *Breaking* Move route class methods to `Routes` class instead of `App`
|
33
|
+
- *Breaking* Create migration class, use migration classes in migrate!
|
34
|
+
- *Breaking* Make routes take symbols or strings, not classes to better line up with models
|
35
|
+
- *Breaking* Move connection string handling to database
|
36
|
+
- Make everything use `frozen_string_literal`
|
37
|
+
- Purposefully never add callbacks, `before_action` or autoloading
|
38
|
+
|
39
|
+
# 1.0.2 (2021-12-02)
|
40
|
+
|
41
|
+
- Set default views / migrations paths
|
42
|
+
|
43
|
+
# 1.0.1 (2021-12-02)
|
44
|
+
|
45
|
+
- Fix changelog link in gemspec
|
46
|
+
|
47
|
+
# 1.0.0 (2021-12-01)
|
7
48
|
|
8
49
|
- Very basic understanding of how much memory it takes for a basic ruby app
|
9
50
|
- Start to integrate controllers and routing
|
@@ -25,3 +66,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
25
66
|
- Use Rack::Test
|
26
67
|
- Rename routes to resources; add resource method
|
27
68
|
- Add nested resources, multi-argument resources
|
69
|
+
- Use rack sessions, rack-csrf and rack-flash3
|
70
|
+
- Add naive migrations
|
71
|
+
- Add naive models
|
72
|
+
- Add naive form helper
|
data/Gemfile
CHANGED
data/README.md
CHANGED
data/exe/rad
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/radical/generator'
|
4
|
+
require_relative '../lib/radical/database'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
@options = {}
|
8
|
+
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.on('-v', '--verbose', 'Show extra information') do
|
11
|
+
@options[:verbose] = true
|
12
|
+
end
|
13
|
+
|
14
|
+
opts.on('-h', '--help', 'Show help information') do
|
15
|
+
@options[:help] = true
|
16
|
+
end
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
generate = Radical::Generator.new(ARGV[2], ARGV.drop(3)) if %w[g generate].include?(ARGV.first)
|
20
|
+
|
21
|
+
case ARGV[0..1]
|
22
|
+
when %w[g mvc], %w[generate mvc]
|
23
|
+
generate.mvc
|
24
|
+
when %w[g model], %w[generate model]
|
25
|
+
generate.migration
|
26
|
+
generate.model
|
27
|
+
when %w[g controller], %w[generate controller]
|
28
|
+
generate.controller
|
29
|
+
when %w[g views], %w[generate views]
|
30
|
+
generate.views
|
31
|
+
when %w[g migration], %w[generate migration]
|
32
|
+
generate.migration(model: false)
|
33
|
+
when %w[g app]
|
34
|
+
generate.app
|
35
|
+
when %w[migrate]
|
36
|
+
Radical::Database.migrate!
|
37
|
+
when %w[rollback]
|
38
|
+
Radical::Database.rollback!
|
39
|
+
else
|
40
|
+
puts 'Command not supported'
|
41
|
+
end
|
data/lib/radical/app.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
1
4
|
require 'rack'
|
2
|
-
require 'rack/flash'
|
3
5
|
require 'rack/csrf'
|
4
6
|
|
5
|
-
require_relative '
|
7
|
+
require_relative 'asset'
|
8
|
+
require_relative 'assets'
|
9
|
+
require_relative 'asset_compiler'
|
6
10
|
require_relative 'env'
|
11
|
+
require_relative 'flash'
|
12
|
+
require_relative 'routes'
|
13
|
+
require_relative 'security_headers'
|
7
14
|
|
8
15
|
# The main entry point for a Radical application
|
9
16
|
#
|
@@ -31,24 +38,39 @@ require_relative 'env'
|
|
31
38
|
module Radical
|
32
39
|
class App
|
33
40
|
class << self
|
34
|
-
def
|
35
|
-
|
41
|
+
def routes(route_class)
|
42
|
+
@routes = route_class
|
36
43
|
end
|
37
44
|
|
38
|
-
def
|
39
|
-
|
45
|
+
def assets(&block)
|
46
|
+
@assets = Assets.new
|
47
|
+
|
48
|
+
block.call(@assets)
|
40
49
|
end
|
41
50
|
|
42
|
-
def
|
43
|
-
|
51
|
+
def compile_assets
|
52
|
+
@assets.compile
|
53
|
+
end
|
44
54
|
|
45
|
-
|
55
|
+
def serve_assets
|
56
|
+
@serve_assets = true
|
57
|
+
end
|
46
58
|
|
47
|
-
|
59
|
+
def security_headers(headers = {})
|
60
|
+
@security_headers = headers
|
61
|
+
end
|
62
|
+
|
63
|
+
def session(options = {})
|
64
|
+
defaults = {
|
65
|
+
path: '/',
|
66
|
+
secret: session_secret,
|
67
|
+
http_only: true,
|
68
|
+
same_site: :lax,
|
69
|
+
secure: env.production?,
|
70
|
+
expire_after: 2_592_000 # 30 days
|
71
|
+
}
|
48
72
|
|
49
|
-
@
|
50
|
-
@parents << classes.last
|
51
|
-
block.call
|
73
|
+
@session = defaults.merge(options)
|
52
74
|
end
|
53
75
|
|
54
76
|
def env
|
@@ -56,8 +78,12 @@ module Radical
|
|
56
78
|
end
|
57
79
|
|
58
80
|
def app
|
59
|
-
router =
|
81
|
+
router = @routes.router
|
60
82
|
env = self.env
|
83
|
+
assets = @assets
|
84
|
+
serve_assets = @serve_assets
|
85
|
+
security_headers = @security_headers || {}
|
86
|
+
session = @session || self.session
|
61
87
|
|
62
88
|
@app ||= Rack::Builder.app do
|
63
89
|
use Rack::CommonLogger
|
@@ -65,23 +91,29 @@ module Radical
|
|
65
91
|
use Rack::Runtime
|
66
92
|
use Rack::MethodOverride
|
67
93
|
use Rack::ContentLength
|
68
|
-
use Rack::Deflater
|
69
94
|
use Rack::ETag
|
95
|
+
use Rack::Deflater
|
70
96
|
use Rack::Head
|
71
97
|
use Rack::ConditionalGet
|
72
98
|
use Rack::ContentType
|
73
|
-
use Rack::Session::Cookie,
|
74
|
-
secret: ENV['SESSION_SECRET'],
|
75
|
-
http_only: true,
|
76
|
-
same_site: :lax,
|
77
|
-
secure: env.production?,
|
78
|
-
expire_after: 2_592_000 # 30 days
|
99
|
+
use Rack::Session::Cookie, session
|
79
100
|
use Rack::Csrf, raise: env.development?, skip: router.routes.values.flatten.select { |a| a.is_a?(Class) }.uniq.map(&:skip_csrf_actions).flatten(1)
|
80
|
-
use
|
101
|
+
use Flash
|
102
|
+
use SecurityHeaders, security_headers
|
103
|
+
|
104
|
+
if serve_assets || env.development?
|
105
|
+
use Rack::Static, urls: ['/assets', '/public'],
|
106
|
+
header_rules: [
|
107
|
+
[/\.(?:css\.gz)$/, { 'Content-Type' => 'text/css', 'Content-Encoding' => 'gzip' }],
|
108
|
+
[/\.(?:js\.gz)$/, { 'Content-Type' => 'application/javascript', 'Content-Encoding' => 'gzip' }],
|
109
|
+
[/\.(?:css\.br)$/, { 'Content-Type' => 'text/css', 'Content-Encoding' => 'br' }],
|
110
|
+
[/\.(?:js\.br)$/, { 'Content-Type' => 'application/javascript', 'Content-Encoding' => 'br' }]
|
111
|
+
]
|
112
|
+
end
|
81
113
|
|
82
114
|
run lambda { |rack_env|
|
83
115
|
begin
|
84
|
-
router.route(Rack::Request.new(rack_env)).finish
|
116
|
+
router.route(Rack::Request.new(rack_env), options: { assets: assets }).finish
|
85
117
|
rescue ModelNotFound
|
86
118
|
raise unless env.production?
|
87
119
|
|
@@ -91,13 +123,15 @@ module Radical
|
|
91
123
|
end
|
92
124
|
end
|
93
125
|
|
94
|
-
def router
|
95
|
-
@router ||= Router.new
|
96
|
-
end
|
97
|
-
|
98
126
|
def call(env)
|
99
127
|
app.call(env)
|
100
128
|
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def session_secret
|
133
|
+
@session_secret ||= (ENV['SESSION_SECRET'] || SecureRandom.hex(32))
|
134
|
+
end
|
101
135
|
end
|
102
136
|
end
|
103
137
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Radical
|
4
|
+
class Asset
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(filename, path:)
|
8
|
+
@filename = filename
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
|
12
|
+
def full_path
|
13
|
+
File.join(path, @filename)
|
14
|
+
end
|
15
|
+
|
16
|
+
def content
|
17
|
+
File.read(full_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def ext
|
21
|
+
File.extname(@filename)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'brotli'
|
4
|
+
require 'digest'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
module Radical
|
8
|
+
class AssetCompiler
|
9
|
+
def self.gzip(filename, content)
|
10
|
+
# nil, 31 == support for gunzip
|
11
|
+
File.write(filename, Zlib::Deflate.new(nil, 31).deflate(content, Zlib::FINISH))
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.compile(assets, path:, compressor: :none)
|
15
|
+
s = assets.map(&:content).join("\n")
|
16
|
+
ext = assets.first.ext
|
17
|
+
|
18
|
+
# hash the contents of each concatenated asset
|
19
|
+
hash = Digest::SHA1.hexdigest s
|
20
|
+
|
21
|
+
# use hash to bust the cache
|
22
|
+
name = "#{hash}#{ext}"
|
23
|
+
filename = File.join(path, name)
|
24
|
+
|
25
|
+
case compressor
|
26
|
+
when :gzip
|
27
|
+
name = "#{name}.gz"
|
28
|
+
gzip("#{filename}.gz", s)
|
29
|
+
when :brotli
|
30
|
+
name = "#{name}.br"
|
31
|
+
File.write("#{filename}.br", Brotli.deflate(s, mode: :text, quality: 11))
|
32
|
+
else
|
33
|
+
File.write(filename, s)
|
34
|
+
end
|
35
|
+
|
36
|
+
# output asset path for browser
|
37
|
+
"/assets/#{name}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Radical
|
4
|
+
class Assets
|
5
|
+
attr_accessor :assets_path, :compiled, :assets
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@assets = {
|
9
|
+
css: [],
|
10
|
+
js: []
|
11
|
+
}
|
12
|
+
@compressor = :none
|
13
|
+
@assets_path = File.join(__dir__, 'assets')
|
14
|
+
@compiled = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def css(filenames)
|
18
|
+
@assets[:css] = filenames
|
19
|
+
end
|
20
|
+
|
21
|
+
def js(filenames)
|
22
|
+
@assets[:js] = filenames
|
23
|
+
end
|
24
|
+
|
25
|
+
def prepend_assets_path(path)
|
26
|
+
@assets_path = File.join(path, 'assets')
|
27
|
+
end
|
28
|
+
|
29
|
+
def brotli
|
30
|
+
@compressor = :brotli
|
31
|
+
end
|
32
|
+
|
33
|
+
def gzip
|
34
|
+
@compressor = :gzip
|
35
|
+
end
|
36
|
+
|
37
|
+
def compile
|
38
|
+
css = @assets[:css].map { |f| Asset.new(f, path: File.join(@assets_path, 'css')) }
|
39
|
+
js = @assets[:js].map { |f| Asset.new(f, path: File.join(@assets_path, 'js')) }
|
40
|
+
|
41
|
+
@compiled[:css] = AssetCompiler.compile(css, path: @assets_path, compressor: @compressor)
|
42
|
+
@compiled[:js] = AssetCompiler.compile(js, path: @assets_path, compressor: @compressor)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/radical/controller.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require 'rack/utils'
|
3
5
|
require 'rack/request'
|
4
6
|
require 'rack/response'
|
@@ -29,7 +31,7 @@ module Radical
|
|
29
31
|
|
30
32
|
sig { returns(String) }
|
31
33
|
def route_name
|
32
|
-
to_s.split('::').last.gsub(/Controller$/, '')
|
34
|
+
Strings.snake_case to_s.split('::').last.gsub(/Controller$/, '')
|
33
35
|
end
|
34
36
|
|
35
37
|
sig { params(actions: Symbol).void }
|
@@ -70,9 +72,12 @@ module Radical
|
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
+
attr_reader :options
|
76
|
+
|
77
|
+
sig { params(request: Rack::Request, options: T.nilable(Hash)).void }
|
78
|
+
def initialize(request, options: {})
|
75
79
|
@request = request
|
80
|
+
@options = options
|
76
81
|
end
|
77
82
|
|
78
83
|
sig { params(status: T.any(Symbol, Integer)).returns(Rack::Response) }
|
@@ -90,14 +95,14 @@ module Radical
|
|
90
95
|
@request.params
|
91
96
|
end
|
92
97
|
|
93
|
-
sig { params(name: T.any(String, Symbol)).returns(String) }
|
94
|
-
def view(name)
|
95
|
-
View.render(self.class.route_name, name, self)
|
98
|
+
sig { params(name: T.any(String, Symbol), locals: T.nilable(Hash)).returns(String) }
|
99
|
+
def view(name, locals = {})
|
100
|
+
View.render(self.class.route_name, name, self, { locals: locals })
|
96
101
|
end
|
97
102
|
|
98
|
-
sig { params(name: T.any(String, Symbol)).returns(String) }
|
99
|
-
def partial(name)
|
100
|
-
View.render(self.class.route_name, "_#{name}", self, layout: false)
|
103
|
+
sig { params(name: T.any(String, Symbol), locals: T.nilable(Hash)).returns(String) }
|
104
|
+
def partial(name, locals = {})
|
105
|
+
View.render(self.class.route_name, "_#{name}", self, { locals: locals, layout: false })
|
101
106
|
end
|
102
107
|
|
103
108
|
sig { params(options: Hash, block: T.proc.void).returns(String) }
|
@@ -128,17 +133,55 @@ module Radical
|
|
128
133
|
@request.env['rack.session']
|
129
134
|
end
|
130
135
|
|
136
|
+
def assets_path(type)
|
137
|
+
assets = options[:assets]
|
138
|
+
|
139
|
+
if Env.production?
|
140
|
+
compiled_assets_path(assets, type)
|
141
|
+
else
|
142
|
+
not_compiled_assets_path(assets, type)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def compiled_assets_path(assets, type)
|
147
|
+
if type == :css
|
148
|
+
link_tag(assets.compiled[:css])
|
149
|
+
else
|
150
|
+
script_tag(assets.compiled[:js])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def not_compiled_assets_path(assets, type)
|
155
|
+
if type == :css
|
156
|
+
assets.assets[:css].map do |asset|
|
157
|
+
link_tag("/assets/#{type}/#{asset}")
|
158
|
+
end.join("\n")
|
159
|
+
else
|
160
|
+
assets.assets[:js].map do |asset|
|
161
|
+
script_tag("/assets/#{type}/#{asset}")
|
162
|
+
end.join("\n")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
131
166
|
private
|
132
167
|
|
133
168
|
def emit(tag)
|
134
|
-
@output =
|
169
|
+
@output = String.new if @output.nil?
|
135
170
|
@output << tag.to_s
|
136
171
|
end
|
137
172
|
|
138
173
|
def capture(block)
|
139
|
-
@output = eval
|
174
|
+
@output = eval '_buf', block.binding
|
140
175
|
yield
|
141
176
|
@output
|
142
177
|
end
|
178
|
+
|
179
|
+
def script_tag(src)
|
180
|
+
"<script type=\"application/javascript\" src=\"#{src}\"></script>"
|
181
|
+
end
|
182
|
+
|
183
|
+
def link_tag(href)
|
184
|
+
"<link rel=\"stylesheet\" type=\"text/css\" href=\"#{href}\" />"
|
185
|
+
end
|
143
186
|
end
|
144
187
|
end
|
data/lib/radical/database.rb
CHANGED
@@ -1,40 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'sqlite3'
|
2
4
|
require_relative 'table'
|
5
|
+
require_relative 'migration'
|
3
6
|
|
4
7
|
module Radical
|
5
8
|
class Database
|
6
9
|
class << self
|
7
|
-
|
10
|
+
attr_writer :connection_string
|
11
|
+
attr_accessor :migrations_path
|
12
|
+
|
13
|
+
def connection_string
|
14
|
+
@connection_string || ENV['DATABASE_URL']
|
15
|
+
end
|
16
|
+
|
17
|
+
def connection
|
18
|
+
conn = SQLite3::Database.new(connection_string)
|
19
|
+
conn.results_as_hash = true
|
20
|
+
conn.type_translation = true
|
21
|
+
|
22
|
+
@connection ||= conn
|
23
|
+
end
|
24
|
+
|
25
|
+
def prepend_migrations_path(path)
|
26
|
+
self.migrations_path = path
|
27
|
+
end
|
8
28
|
|
9
29
|
def db
|
10
30
|
connection
|
11
31
|
end
|
12
32
|
|
13
|
-
def
|
14
|
-
|
33
|
+
def migration(file)
|
34
|
+
context = Module.new
|
35
|
+
context.class_eval(File.read(file), file)
|
36
|
+
const = context.constants.find do |constant|
|
37
|
+
context.const_get(constant).ancestors.include?(Radical::Migration)
|
38
|
+
end
|
39
|
+
|
40
|
+
context.const_get(const)
|
41
|
+
end
|
15
42
|
|
43
|
+
def migrate!
|
16
44
|
db.execute 'create table if not exists radical_migrations ( version integer primary key )'
|
17
45
|
|
18
|
-
pending_migrations.each do |
|
19
|
-
puts "Executing migration #{
|
20
|
-
|
21
|
-
|
22
|
-
|
46
|
+
pending_migrations.each do |file|
|
47
|
+
puts "Executing migration #{file}"
|
48
|
+
|
49
|
+
v = version(file)
|
50
|
+
|
51
|
+
migration(file).migrate!(db: db, version: v)
|
23
52
|
end
|
24
53
|
end
|
25
54
|
|
26
55
|
def rollback!
|
27
|
-
@rollback = true
|
28
|
-
|
29
56
|
db.execute 'create table if not exists radical_migrations ( version integer primary key )'
|
30
57
|
|
31
|
-
|
58
|
+
file = applied_migrations.last
|
32
59
|
|
33
|
-
puts "Rolling back migration #{
|
60
|
+
puts "Rolling back migration #{file}"
|
34
61
|
|
35
|
-
|
36
|
-
|
37
|
-
db
|
62
|
+
v = version(file)
|
63
|
+
|
64
|
+
migration(file).rollback!(db: db, version: v)
|
38
65
|
end
|
39
66
|
|
40
67
|
def applied_versions
|
@@ -53,40 +80,12 @@ module Radical
|
|
53
80
|
end
|
54
81
|
|
55
82
|
def migrations
|
56
|
-
Dir[File.join(migrations_path
|
83
|
+
Dir[File.join(migrations_path || '.', 'migrations', '*.rb')].sort
|
57
84
|
end
|
58
85
|
|
59
86
|
def version(filename)
|
60
87
|
filename.split(File::SEPARATOR).last.split('_').first.to_i
|
61
88
|
end
|
62
|
-
|
63
|
-
def migration(&block)
|
64
|
-
block.call
|
65
|
-
end
|
66
|
-
|
67
|
-
def change(&block)
|
68
|
-
@change = true
|
69
|
-
|
70
|
-
block.call
|
71
|
-
end
|
72
|
-
|
73
|
-
def up(&block)
|
74
|
-
block.call
|
75
|
-
end
|
76
|
-
|
77
|
-
def down(&block)
|
78
|
-
block.call
|
79
|
-
end
|
80
|
-
|
81
|
-
def create_table(name, &block)
|
82
|
-
return "drop table #{name}" if @change && @rollback
|
83
|
-
|
84
|
-
table = Table.new(name)
|
85
|
-
|
86
|
-
block.call(table)
|
87
|
-
|
88
|
-
"create table #{name} ( id integer primary key, #{table.columns.join(',')} )"
|
89
|
-
end
|
90
89
|
end
|
91
90
|
end
|
92
91
|
end
|
data/lib/radical/env.rb
CHANGED