shaf 0.4.1 → 0.5.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
- checksums.yaml.gz.sig +0 -0
- data/lib/shaf/command/base.rb +51 -0
- data/lib/shaf/command/templates/Gemfile.erb +0 -1
- data/lib/shaf/command.rb +1 -47
- data/lib/shaf/extensions/resource_uris.rb +113 -151
- data/lib/shaf/generator/base.rb +61 -0
- data/lib/shaf/generator/migration/base.rb +133 -0
- data/lib/shaf/generator/migration/create_table.rb +4 -4
- data/lib/shaf/generator/migration.rb +1 -111
- data/lib/shaf/generator/serializer.rb +3 -51
- data/lib/shaf/generator/templates/api/controller.rb.erb +2 -0
- data/lib/shaf/generator/templates/api/policy.rb.erb +3 -2
- data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -6
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +30 -30
- data/lib/shaf/generator.rb +1 -57
- data/lib/shaf/helpers/cache_control.rb +21 -0
- data/lib/shaf/helpers.rb +4 -2
- data/lib/shaf/rake/db.rb +1 -1
- data/lib/shaf/upgrade/manifest.rb +29 -8
- data/lib/shaf/upgrade/package.rb +51 -27
- data/lib/shaf/version.rb +1 -1
- data/templates/api/controllers/docs_controller.rb +2 -0
- data/templates/api/controllers/root_controller.rb +2 -3
- data/templates/api/policies/base_policy.rb +3 -0
- data/templates/api/serializers/base_serializer.rb +4 -0
- data/templates/api/serializers/error_serializer.rb +3 -2
- data/templates/api/serializers/form_serializer.rb +2 -2
- data/templates/api/serializers/root_serializer.rb +3 -3
- data/templates/config/initializers/db_migrations.rb +24 -11
- data/templates/config/initializers/hal_presenter.rb +0 -5
- data/templates/config/settings.yml +8 -3
- data/upgrades/0.5.0.tar.gz +0 -0
- data.tar.gz.sig +0 -0
- metadata +69 -6
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7ef999e9f5ceac2a21b21e33ae805a6f576bc1363c6cbd4622cbbbdf715e8252
|
4
|
+
data.tar.gz: '0030305862359ea34e55516b2d49abcd78c20eb1393ae9d99eabcb86d1b30de4'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ac44a36ff0d58fce2e7fb58d9f85187dc75e1077cc801f5fcfd637738be364619c3ccbb3d5ea28b0cd46319f258d36a6047f4c22f53564487a4af8652569ffd
|
7
|
+
data.tar.gz: d4bddad299a6236d0930d861a2fdc054531f5892c29cc9c2f5c918066d6943c0aa888b9e2fa346a7733784f2311da5845c475632c4641949711a8f0784546d15
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'shaf/utils'
|
3
|
+
|
4
|
+
module Shaf
|
5
|
+
module Command
|
6
|
+
class ArgumentError < CommandError; end
|
7
|
+
|
8
|
+
class Base
|
9
|
+
include Utils
|
10
|
+
|
11
|
+
attr_reader :args, :options
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def inherited(child)
|
15
|
+
Factory.register(child)
|
16
|
+
end
|
17
|
+
|
18
|
+
def identifier(*ids)
|
19
|
+
@identifiers = ids.flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
def usage(str = nil, &block)
|
23
|
+
@usage = str || block
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit_with_error(msg, status)
|
27
|
+
STDERR.puts msg
|
28
|
+
exit status
|
29
|
+
end
|
30
|
+
|
31
|
+
def options(option_parser, options); end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(*args)
|
35
|
+
@args = args.dup
|
36
|
+
@options = {}
|
37
|
+
parse_options!
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def parse_options!
|
43
|
+
parser = OptionParser.new
|
44
|
+
self.class.options(parser, @options)
|
45
|
+
parser.parse!(args)
|
46
|
+
rescue OptionParser::InvalidOption => e
|
47
|
+
raise ArgumentError, e.message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/shaf/command.rb
CHANGED
@@ -1,62 +1,16 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'shaf/utils'
|
3
1
|
require 'shaf/registrable_factory'
|
4
2
|
|
5
3
|
module Shaf
|
6
4
|
module Command
|
7
|
-
|
8
5
|
class CommandError < StandardError; end
|
9
|
-
class ArgumentError < CommandError; end
|
10
6
|
|
11
7
|
class Factory
|
12
8
|
extend RegistrableFactory
|
13
9
|
end
|
14
|
-
|
15
|
-
class Base
|
16
|
-
include Utils
|
17
|
-
|
18
|
-
attr_reader :args, :options
|
19
|
-
|
20
|
-
class << self
|
21
|
-
def inherited(child)
|
22
|
-
Factory.register(child)
|
23
|
-
end
|
24
|
-
|
25
|
-
def identifier(*ids)
|
26
|
-
@identifiers = ids.flatten
|
27
|
-
end
|
28
|
-
|
29
|
-
def usage(str = nil, &block)
|
30
|
-
@usage = str || block
|
31
|
-
end
|
32
|
-
|
33
|
-
def exit_with_error(msg, status)
|
34
|
-
STDERR.puts msg
|
35
|
-
exit status
|
36
|
-
end
|
37
|
-
|
38
|
-
def options(option_parser, options); end
|
39
|
-
end
|
40
|
-
|
41
|
-
def initialize(*args)
|
42
|
-
@args = args.dup
|
43
|
-
@options = {}
|
44
|
-
parse_options!
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def parse_options!
|
50
|
-
parser = OptionParser.new
|
51
|
-
self.class.options(parser, @options)
|
52
|
-
parser.parse!(args)
|
53
|
-
rescue OptionParser::InvalidOption => e
|
54
|
-
raise ArgumentError, e.message
|
55
|
-
end
|
56
|
-
end
|
57
10
|
end
|
58
11
|
end
|
59
12
|
|
13
|
+
require 'shaf/command/base'
|
60
14
|
require 'shaf/command/new'
|
61
15
|
require 'shaf/command/upgrade'
|
62
16
|
require 'shaf/command/server'
|
@@ -9,12 +9,7 @@ module Shaf
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def register_uri(name, uri)
|
12
|
-
|
13
|
-
raise "resource uri #{name} can't be registered. Method already exist!"
|
14
|
-
end
|
15
|
-
method_string = MethodBuilder.as_string(name, uri)
|
16
|
-
UriHelperMethods.eval_method(method_string)
|
17
|
-
UriHelperMethods.register(MethodBuilder.template_method_name(name)) { uri.dup.freeze }
|
12
|
+
MethodBuilder.new(name, uri).call
|
18
13
|
|
19
14
|
include UriHelper unless self < UriHelper
|
20
15
|
end
|
@@ -39,6 +34,13 @@ module Shaf
|
|
39
34
|
def self.included(mod)
|
40
35
|
mod.extend self
|
41
36
|
end
|
37
|
+
|
38
|
+
def self.base_uri
|
39
|
+
protocol = Settings.protocol || 'http'
|
40
|
+
host = Settings.host || 'localhost'
|
41
|
+
port = Settings.port ? ":#{Settings.port}" : ""
|
42
|
+
"#{protocol}://#{host}#{port}"
|
43
|
+
end
|
42
44
|
end
|
43
45
|
|
44
46
|
# This class register uri helper methods like:
|
@@ -55,52 +57,10 @@ module Shaf
|
|
55
57
|
#
|
56
58
|
class CreateUriMethods
|
57
59
|
|
58
|
-
# Resources should never be nested more than 1 level deep.
|
59
|
-
MAX_NESTING_DEPTH = 1
|
60
|
-
|
61
|
-
class UriTemplateNestingError < StandardError
|
62
|
-
def initialize(msg = nil)
|
63
|
-
msg ||= "Uri templates only supports a nesting depth of #{MAX_NESTING_DEPTH}"
|
64
|
-
super(msg)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class UriTemplateVariableError < StandardError
|
69
|
-
def initialize(msg = nil)
|
70
|
-
msg ||= "Mismatch between uri templates and resources"
|
71
|
-
super(msg)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class << self
|
76
|
-
def resource_helper_uri(template_uri, *resources, query)
|
77
|
-
uri = replace_templates(template_uri, *resources)
|
78
|
-
query_string = MethodBuilder.query_string(query)
|
79
|
-
"#{uri}#{query_string}".freeze
|
80
|
-
end
|
81
|
-
|
82
|
-
def replace_templates(template_uri, *resources)
|
83
|
-
symbols = MethodBuilder.extract_symbols(template_uri)
|
84
|
-
resources.compact!
|
85
|
-
raise UriTemplateVariableError if symbols.size != resources.size
|
86
|
-
|
87
|
-
MethodBuilder.transform_symbols(template_uri) do |segment|
|
88
|
-
resrc = resources.shift
|
89
|
-
sym = symbols.shift
|
90
|
-
resrc.respond_to?(sym) ? resrc.public_send(sym) : resrc
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
60
|
def initialize(name, base: nil, plural_name: nil)
|
96
61
|
@name = name.to_s
|
97
62
|
@base = base&.sub(%r(/\Z), '') || ''
|
98
63
|
@plural_name = plural_name&.to_s || Utils::pluralize(name.to_s)
|
99
|
-
|
100
|
-
if nesting_depth > MAX_NESTING_DEPTH
|
101
|
-
raise UriTemplateNestingError,
|
102
|
-
"Too deep nesting level (max #{MAX_NESTING_DEPTH}): #{@base}"
|
103
|
-
end
|
104
64
|
end
|
105
65
|
|
106
66
|
def call
|
@@ -120,14 +80,12 @@ module Shaf
|
|
120
80
|
|
121
81
|
def register_resources_uri
|
122
82
|
template_uri = "#{base}/#{plural_name}".freeze
|
123
|
-
|
124
|
-
register(template_uri, helper_name)
|
83
|
+
register(plural_name, template_uri)
|
125
84
|
end
|
126
85
|
|
127
86
|
def register_resource_uri
|
128
87
|
template_uri = "#{base}/#{plural_name}/:id".freeze
|
129
|
-
|
130
|
-
register(template_uri, helper_name)
|
88
|
+
register(name, template_uri)
|
131
89
|
end
|
132
90
|
|
133
91
|
# If a resource has the same singular and plural names, then this method
|
@@ -136,137 +94,141 @@ module Shaf
|
|
136
94
|
def register_resource_uri_by_arg
|
137
95
|
resource_template_uri = "#{base}/#{plural_name}/:id"
|
138
96
|
collection_template_uri = "#{base}/#{plural_name}"
|
139
|
-
helper_name = "#{plural_name}_uri"
|
140
97
|
|
141
|
-
|
142
|
-
UriHelperMethods.register(helper_name, &block)
|
143
|
-
UriHelperMethods.register("#{helper_name}_template") do |collection = false|
|
144
|
-
(collection ? collection_template_uri : resource_template_uri).freeze
|
145
|
-
end
|
98
|
+
MethodBuilder.new(name, resource_template_uri, alt_uri: collection_template_uri).call
|
146
99
|
end
|
147
100
|
|
148
101
|
def register_new_resource_uri
|
149
102
|
template_uri = "#{base}/#{plural_name}/form".freeze
|
150
|
-
|
151
|
-
register(template_uri, helper_name)
|
103
|
+
register("new_#{name}", template_uri)
|
152
104
|
end
|
153
105
|
|
154
106
|
def register_edit_resource_uri
|
155
107
|
template_uri = "#{base}/#{plural_name}/:id/edit".freeze
|
156
|
-
|
157
|
-
register(template_uri, helper_name)
|
108
|
+
register("edit_#{name}", template_uri)
|
158
109
|
end
|
159
110
|
|
160
|
-
def register(
|
161
|
-
|
162
|
-
UriHelperMethods.register(helper_name, &block)
|
163
|
-
UriHelperMethods.register("#{helper_name}_template") { template_uri }
|
111
|
+
def register(name, template_uri)
|
112
|
+
MethodBuilder.new(name, template_uri).call
|
164
113
|
end
|
114
|
+
end
|
165
115
|
|
166
|
-
|
167
|
-
|
116
|
+
class MethodBuilder
|
117
|
+
def self.query_string(query)
|
118
|
+
return "" unless query.any?
|
119
|
+
"?#{query.map { |key,value| "#{key}=#{value}" }.join("&")}"
|
120
|
+
end
|
168
121
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
end
|
174
|
-
when 1
|
175
|
-
->(resrc, **query) do
|
176
|
-
CreateUriMethods.resource_helper_uri(template_uri, resrc, query)
|
177
|
-
end
|
178
|
-
when 2
|
179
|
-
->(parent_resrc, resrc, **query) do
|
180
|
-
CreateUriMethods.resource_helper_uri(template_uri, parent_resrc, resrc, query)
|
181
|
-
end
|
182
|
-
else
|
183
|
-
raise UriTemplateNestingError,
|
184
|
-
"Too deep nesting level (max #{MAX_NESTING_DEPTH}): #{template_uri}"
|
185
|
-
end
|
122
|
+
def initialize(name, uri, alt_uri: nil)
|
123
|
+
@name = name
|
124
|
+
@uri = uri
|
125
|
+
@alt_uri = alt_uri
|
186
126
|
end
|
187
127
|
|
188
|
-
def
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
CreateUriMethods.resource_helper_uri(*args)
|
198
|
-
}
|
199
|
-
when 1
|
200
|
-
->(parent_resrc, resrc = nil, **query) {
|
201
|
-
args = if resrc.nil?
|
202
|
-
[collection_template_uri, parent_resrc, query]
|
203
|
-
else
|
204
|
-
[resource_template_uri, parent_resrc, resrc, query]
|
205
|
-
end
|
206
|
-
CreateUriMethods.resource_helper_uri(*args)
|
207
|
-
}
|
128
|
+
def call
|
129
|
+
if UriHelper.respond_to? method_name
|
130
|
+
raise "resource uri #{@name} can't be registered. " \
|
131
|
+
"Method :#{method_name} already exist!"
|
132
|
+
end
|
133
|
+
|
134
|
+
if @alt_uri.nil?
|
135
|
+
build_methods
|
208
136
|
else
|
209
|
-
|
210
|
-
"Too deep nesting level (max #{MAX_NESTING_DEPTH}): #{template_uri}"
|
137
|
+
build_methods_with_optional_arg
|
211
138
|
end
|
212
139
|
end
|
213
140
|
|
214
|
-
|
215
|
-
|
141
|
+
private
|
142
|
+
|
143
|
+
def build_methods
|
144
|
+
UriHelperMethods.eval_method method_string
|
145
|
+
UriHelperMethods.register(template_method_name, &template_proc)
|
216
146
|
end
|
217
|
-
end
|
218
147
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
end
|
148
|
+
def build_methods_with_optional_arg
|
149
|
+
UriHelperMethods.eval_method method_with_optional_arg_string
|
150
|
+
UriHelperMethods.register(template_method_name, &template_proc)
|
151
|
+
end
|
224
152
|
|
225
|
-
|
226
|
-
|
227
|
-
|
153
|
+
def method_name
|
154
|
+
"#{@name}_uri".freeze
|
155
|
+
end
|
228
156
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
s << (args.empty? ? "**query)" : "#{args.join(', ')}, **query)")
|
233
|
-
end
|
157
|
+
def template_method_name
|
158
|
+
"#{method_name}_template".freeze
|
159
|
+
end
|
234
160
|
|
235
|
-
|
236
|
-
|
237
|
-
"?#{query.map { |key,value| "#{key}=#{value}" }.join("&")}"
|
238
|
-
end
|
161
|
+
def signature(optional_args: 0)
|
162
|
+
s = "#{method_name}("
|
239
163
|
|
240
|
-
|
241
|
-
|
242
|
-
<<~Ruby
|
243
|
-
def #{signature}
|
244
|
-
query_string = MethodBuilder.query_string(query)
|
245
|
-
\"#{interpolated_uri_string(uri)}\#{query_string}\".freeze
|
246
|
-
end
|
247
|
-
Ruby
|
248
|
-
end
|
164
|
+
symbols = extract_symbols.size.times.map { |i| "arg#{i}" }
|
165
|
+
sym_count = symbols.size
|
249
166
|
|
250
|
-
|
251
|
-
|
167
|
+
args = []
|
168
|
+
symbols.each_with_index do |arg, i|
|
169
|
+
if i < sym_count - optional_args
|
170
|
+
args << "arg#{i}"
|
171
|
+
else
|
172
|
+
args << "arg#{i} = nil"
|
173
|
+
end
|
252
174
|
end
|
175
|
+
s << (args.empty? ? "**query)" : "#{args.join(', ')}, **query)")
|
176
|
+
end
|
253
177
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
178
|
+
def method_string
|
179
|
+
base_uri = UriHelper.base_uri
|
180
|
+
<<~Ruby
|
181
|
+
def #{signature}
|
182
|
+
query_str = Shaf::MethodBuilder.query_string(query)
|
183
|
+
\"#{base_uri}#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
|
184
|
+
end
|
185
|
+
Ruby
|
186
|
+
end
|
187
|
+
|
188
|
+
def method_with_optional_arg_string
|
189
|
+
base_uri = UriHelper.base_uri
|
190
|
+
arg_no = extract_symbols.size - 1
|
191
|
+
<<~Ruby
|
192
|
+
def #{signature(optional_args: 1)}
|
193
|
+
query_str = Shaf::MethodBuilder.query_string(query)
|
194
|
+
if arg#{arg_no}.nil?
|
195
|
+
\"#{base_uri}#{interpolated_uri_string(@alt_uri)}\#{query_str}\".freeze
|
196
|
+
else
|
197
|
+
\"#{base_uri}#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
|
198
|
+
end
|
199
|
+
end
|
200
|
+
Ruby
|
201
|
+
end
|
202
|
+
|
203
|
+
def extract_symbols(uri = @uri)
|
204
|
+
uri.split('/').grep(/:.*/).map { |t| t[1..-1].to_sym }
|
205
|
+
end
|
260
206
|
|
261
|
-
|
207
|
+
def transform_symbols(uri)
|
208
|
+
i = -1
|
209
|
+
uri.split('/').map do |segment|
|
210
|
+
next segment unless segment.start_with? ':'
|
211
|
+
i += 1
|
212
|
+
yield segment, i
|
213
|
+
end.join('/')
|
214
|
+
end
|
262
215
|
|
263
|
-
|
264
|
-
|
216
|
+
def interpolated_uri_string(uri)
|
217
|
+
return uri if uri == '/'
|
265
218
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
219
|
+
transform_symbols(uri) do |segment, i|
|
220
|
+
sym = segment[1..-1]
|
221
|
+
"\#{arg#{i}.respond_to?(#{segment}) ? arg#{i}.#{sym} : arg#{i}}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def template_proc
|
226
|
+
uri, alt_uri = @uri, @alt_uri
|
227
|
+
|
228
|
+
if alt_uri.nil?
|
229
|
+
-> { uri.freeze }
|
230
|
+
else
|
231
|
+
->(collection = false) { collection ? alt_uri : uri }
|
270
232
|
end
|
271
233
|
end
|
272
234
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'erb'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
module Generator
|
7
|
+
class Base
|
8
|
+
attr_reader :args
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def inherited(child)
|
12
|
+
Factory.register(child)
|
13
|
+
end
|
14
|
+
|
15
|
+
def identifier(*ids)
|
16
|
+
@identifiers = ids.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def usage(str = nil, &block)
|
20
|
+
@usage = str || block
|
21
|
+
end
|
22
|
+
|
23
|
+
def options(option_parser, options); end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(*args)
|
27
|
+
@args = args.dup
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(options = {}); end
|
31
|
+
|
32
|
+
def template_dir
|
33
|
+
File.expand_path('../templates', __FILE__)
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_template(file, directory = nil)
|
37
|
+
directory ||= template_dir
|
38
|
+
filename = File.join(directory, file)
|
39
|
+
filename << ".erb" unless filename.end_with?(".erb")
|
40
|
+
File.read(filename)
|
41
|
+
end
|
42
|
+
|
43
|
+
def render(template, locals = {})
|
44
|
+
str = read_template(template)
|
45
|
+
locals[:changes] ||= []
|
46
|
+
b = OpenStruct.new(locals).instance_eval { binding }
|
47
|
+
ERB.new(str, 0, '%-<>').result(b)
|
48
|
+
rescue SystemCallError => e
|
49
|
+
puts "Failed to render template #{template}: #{e.message}"
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_output(file, content)
|
54
|
+
dir = File.dirname(file)
|
55
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
56
|
+
File.write(file, content)
|
57
|
+
puts "Added: #{file}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Generator
|
5
|
+
module Migration
|
6
|
+
class Base
|
7
|
+
DB_COL_FORMAT_STRINGS = {
|
8
|
+
integer: ['Integer :%s', 'add_column :%s, Integer'],
|
9
|
+
varchar: ['String %s', 'add_column :%s, String'],
|
10
|
+
string: ['String :%s', 'add_column :%s, String'],
|
11
|
+
text: ['String :%s, text: true', 'add_column :%s, String, text: true'],
|
12
|
+
blob: ['File :%s', 'add_column :%s, File'],
|
13
|
+
bigint: ['Bignum :%s', 'add_column :%s, Bignum'],
|
14
|
+
double: ['Float :%s', 'add_column :%s, Float'],
|
15
|
+
numeric: ['BigDecimal :%s', 'add_column :%s, BigDecimal'],
|
16
|
+
date: ['Date :%s', 'add_column :%s, Date'],
|
17
|
+
timestamp: ['DateTime :%s', 'add_column :%s, DateTime'],
|
18
|
+
time: ['Time :%s', 'add_column :%s, Time'],
|
19
|
+
bool: ['TrueClass :%s', 'add_column :%s, TrueClass'],
|
20
|
+
boolean: ['TrueClass :%s', 'add_column :%s, TrueClass'],
|
21
|
+
index: ['index :%s, unique: true', 'add_index :%s'],
|
22
|
+
}
|
23
|
+
|
24
|
+
COMPLEX_DB_TYPES = [
|
25
|
+
{
|
26
|
+
pattern: /\Aforeign_key\((\w+)\)/,
|
27
|
+
strings: ['foreign_key :%s, :\1', 'add_foreign_key :%s, :\1'],
|
28
|
+
validator: ->(type, match) {
|
29
|
+
break if ::DB.table_exists?(match[1])
|
30
|
+
["Foreign key table '#{match[1]}' does not exist!"]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
]
|
34
|
+
|
35
|
+
attr_reader :args
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def inherited(child)
|
39
|
+
Factory.register(child)
|
40
|
+
end
|
41
|
+
|
42
|
+
def identifier(*ids)
|
43
|
+
@identifiers = ids.flatten.map(&:to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def usage(str = nil, &block)
|
47
|
+
@usage = str || block
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(*args)
|
52
|
+
@args = args.dup
|
53
|
+
end
|
54
|
+
|
55
|
+
def call
|
56
|
+
validate_args
|
57
|
+
name = compile_migration_name
|
58
|
+
compile_changes
|
59
|
+
[target(name), render]
|
60
|
+
rescue StandardError => e
|
61
|
+
raise Command::ArgumentError, e.message
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_change(change)
|
65
|
+
@changes ||= []
|
66
|
+
@changes << change if change
|
67
|
+
end
|
68
|
+
|
69
|
+
def column_def(str, create: true)
|
70
|
+
name, type = str.split(':')
|
71
|
+
format_string = db_format_string(type, create ? 0 : 1)
|
72
|
+
format format_string, name.downcase
|
73
|
+
end
|
74
|
+
|
75
|
+
def target(name)
|
76
|
+
raise "Migration filename is nil" unless name
|
77
|
+
"db/migrations/#{timestamp}_#{name}.rb"
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def timestamp
|
83
|
+
DateTime.now.strftime("%Y%m%d%H%M%S")
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_timestamp_columns?
|
87
|
+
if File.exist? 'config/initializers/sequel.rb'
|
88
|
+
require 'config/initializers/sequel'
|
89
|
+
Sequel::Model.plugins.include? Sequel::Plugins::Timestamps
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def db_format_string(type, range = 0..1)
|
94
|
+
type ||= :string
|
95
|
+
result = DB_COL_FORMAT_STRINGS[type.to_sym]
|
96
|
+
result ||= regexp_db_format_string(type)
|
97
|
+
raise "Column type '#{type}' not supported" unless result
|
98
|
+
result[range]
|
99
|
+
end
|
100
|
+
|
101
|
+
def regexp_db_format_string(type)
|
102
|
+
COMPLEX_DB_TYPES.each do |complex_type|
|
103
|
+
match = complex_type[:pattern].match(type) or next
|
104
|
+
validator = complex_type[:validator]
|
105
|
+
errors = validator&.call(type, match)
|
106
|
+
if errors.nil? || errors.empty?
|
107
|
+
return complex_type[:strings].map { |s| replace_backreferences(match, s) }
|
108
|
+
end
|
109
|
+
raise "Failed to process '#{type}': #{errors.join(', ')}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def replace_backreferences(match, str)
|
114
|
+
groups = match.size
|
115
|
+
(1...groups).inject(str) do |s, i|
|
116
|
+
s.gsub("\\#{i}", match[i])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def render
|
121
|
+
<<~EOS
|
122
|
+
Sequel.migration do
|
123
|
+
change do
|
124
|
+
#{@changes.flatten.join("\n ")}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
EOS
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -11,14 +11,14 @@ module Shaf
|
|
11
11
|
raise "Please provide a table name when generation a create table migration"
|
12
12
|
end
|
13
13
|
|
14
|
-
def compile_migration_name
|
15
|
-
"create_#{table_name}_table"
|
16
|
-
end
|
17
|
-
|
18
14
|
def table_name
|
19
15
|
args.first || ""
|
20
16
|
end
|
21
17
|
|
18
|
+
def compile_migration_name
|
19
|
+
"create_#{table_name}_table"
|
20
|
+
end
|
21
|
+
|
22
22
|
def compile_changes
|
23
23
|
add_change create_table_change
|
24
24
|
end
|