shaf 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/bin/shaf +3 -0
- data/lib/shaf.rb +1 -0
- data/lib/shaf/api_doc/document.rb +0 -1
- data/lib/shaf/app.rb +1 -1
- data/lib/shaf/command.rb +19 -2
- data/lib/shaf/command/generate.rb +11 -1
- data/lib/shaf/command/new.rb +1 -6
- data/lib/shaf/command/server.rb +7 -0
- data/lib/shaf/command/upgrade.rb +38 -0
- data/lib/shaf/extensions/authorize.rb +8 -6
- data/lib/shaf/extensions/resource_uris.rb +129 -71
- data/lib/shaf/generator.rb +4 -0
- data/lib/shaf/generator/controller.rb +2 -2
- data/lib/shaf/generator/migration.rb +33 -14
- data/lib/shaf/generator/migration/add_column.rb +1 -3
- data/lib/shaf/generator/migration/add_index.rb +42 -0
- data/lib/shaf/generator/model.rb +4 -4
- data/lib/shaf/generator/policy.rb +1 -1
- data/lib/shaf/generator/scaffold.rb +3 -3
- data/lib/shaf/generator/serializer.rb +5 -5
- data/lib/shaf/rake.rb +5 -0
- data/lib/shaf/rake/db.rb +79 -0
- data/lib/shaf/rake/test.rb +32 -0
- data/lib/shaf/registrable_factory.rb +8 -3
- data/lib/shaf/settings.rb +20 -4
- data/lib/shaf/tasks.rb +6 -3
- data/lib/shaf/{api_doc/task.rb → tasks/api_doc_task.rb} +6 -5
- data/lib/shaf/tasks/db_task.rb +42 -0
- data/lib/shaf/tasks/test_task.rb +16 -0
- data/lib/shaf/upgrade.rb +3 -0
- data/lib/shaf/upgrade/manifest.rb +31 -0
- data/lib/shaf/upgrade/package.rb +158 -0
- data/lib/shaf/upgrade/version.rb +52 -0
- data/lib/shaf/utils.rb +30 -0
- data/lib/shaf/version.rb +1 -1
- data/templates/Rakefile +2 -3
- data/templates/config/database.rb +3 -3
- metadata +33 -202
- metadata.gz.sig +0 -0
- data/lib/shaf/api_doc.rb +0 -3
- data/lib/shaf/tasks/db.rb +0 -81
- data/lib/shaf/tasks/test.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 749585960501bd61a7e7f4f22c2a90c2108f86c6cf0698ec87f495bb78d87389
|
4
|
+
data.tar.gz: da5fc9e7f87876f12654d5ea8473e7f0cd4a9dc3daba88055a51f7aaf0a794a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '017559afb07a1d9e142a96c09c406cd697b6c797732661c8f431a1b2803c5c9e8c9c04cd7718037638a440ad077a55eaf1b382a9cdb47687b4bef7921b6ae4c5'
|
7
|
+
data.tar.gz: 3d2a16944a04292fd9039b2dbe5f482950296b7f1dd853e383cdbed972f33755243b16a89b5b081f53da43d456c4b1c8f081ac31ecdbcea66fce3076b2b6ec78
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/bin/shaf
CHANGED
@@ -24,6 +24,9 @@ module Shaf
|
|
24
24
|
STDERR.puts "This command can only be executed inside a Shaf project directory. " \
|
25
25
|
"Please change directory and try again!"
|
26
26
|
exit 2
|
27
|
+
rescue Command::CommandError => e
|
28
|
+
STDERR.puts "Command failed: #{e.message}\n"
|
29
|
+
exit 3
|
27
30
|
end
|
28
31
|
|
29
32
|
def show_help
|
data/lib/shaf.rb
CHANGED
data/lib/shaf/app.rb
CHANGED
data/lib/shaf/command.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
require 'optparse'
|
1
2
|
require 'shaf/utils'
|
2
3
|
require 'shaf/registrable_factory'
|
3
4
|
|
4
5
|
module Shaf
|
5
6
|
module Command
|
6
7
|
|
7
|
-
class
|
8
|
+
class CommandError < StandardError; end
|
9
|
+
class ArgumentError < CommandError; end
|
8
10
|
|
9
11
|
class Factory
|
10
12
|
extend RegistrableFactory
|
@@ -13,7 +15,7 @@ module Shaf
|
|
13
15
|
class Base
|
14
16
|
include Utils
|
15
17
|
|
16
|
-
attr_reader :args
|
18
|
+
attr_reader :args, :options
|
17
19
|
|
18
20
|
class << self
|
19
21
|
def inherited(child)
|
@@ -32,16 +34,31 @@ module Shaf
|
|
32
34
|
STDERR.puts msg
|
33
35
|
exit status
|
34
36
|
end
|
37
|
+
|
38
|
+
def options(option_parser, options); end
|
35
39
|
end
|
36
40
|
|
37
41
|
def initialize(*args)
|
38
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
|
39
55
|
end
|
40
56
|
end
|
41
57
|
end
|
42
58
|
end
|
43
59
|
|
44
60
|
require 'shaf/command/new'
|
61
|
+
require 'shaf/command/upgrade'
|
45
62
|
require 'shaf/command/server'
|
46
63
|
require 'shaf/command/console'
|
47
64
|
require 'shaf/command/generate'
|
@@ -7,9 +7,19 @@ module Shaf
|
|
7
7
|
identifier %r(\Ag(en(erate)?)?\Z)
|
8
8
|
usage Generator::Factory.usage.flatten.sort
|
9
9
|
|
10
|
+
def self.options(parser, options)
|
11
|
+
parser.on("-s", "--[no-]specs", "generate specs") do |s|
|
12
|
+
options[:specs] = s
|
13
|
+
end
|
14
|
+
|
15
|
+
Generator::Factory.each do |clazz|
|
16
|
+
clazz.options(parser, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
10
20
|
def call
|
11
21
|
in_project_root do
|
12
|
-
Generator::Factory.create(*args).call
|
22
|
+
Generator::Factory.create(*args).call(options)
|
13
23
|
end
|
14
24
|
rescue StandardError => e
|
15
25
|
raise Command::ArgumentError, e.message
|
data/lib/shaf/command/new.rb
CHANGED
@@ -21,7 +21,7 @@ module Shaf
|
|
21
21
|
Dir.chdir(@project_name) do
|
22
22
|
copy_templates
|
23
23
|
create_gemfile
|
24
|
-
|
24
|
+
write_shaf_version
|
25
25
|
create_ruby_version_file
|
26
26
|
end
|
27
27
|
end
|
@@ -46,11 +46,6 @@ module Shaf
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def create_shaf_version_file
|
50
|
-
File.write '.shaf',
|
51
|
-
YAML.dump({'version' => Shaf::VERSION})
|
52
|
-
end
|
53
|
-
|
54
49
|
def create_ruby_version_file
|
55
50
|
File.write '.ruby-version', RUBY_VERSION
|
56
51
|
end
|
data/lib/shaf/command/server.rb
CHANGED
@@ -5,7 +5,14 @@ module Shaf
|
|
5
5
|
identifier %r(\As(erver)?\Z)
|
6
6
|
usage 'server'
|
7
7
|
|
8
|
+
def self.options(parser, options)
|
9
|
+
parser.on("-p", "--port PORT", Integer, "Listen port") do |p|
|
10
|
+
options[:port] = p
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
8
14
|
def call
|
15
|
+
Settings.port = options[:port] if options[:port]
|
9
16
|
bootstrap
|
10
17
|
App.instance.run!
|
11
18
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'shaf/upgrade'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Command
|
5
|
+
class Upgrade < Base
|
6
|
+
|
7
|
+
class UnknownShafVersionError < CommandError; end
|
8
|
+
class UpgradeFailedError < CommandError; end
|
9
|
+
|
10
|
+
identifier %r(\Aupgrade\Z)
|
11
|
+
usage 'upgrade'
|
12
|
+
|
13
|
+
def call
|
14
|
+
in_project_root do
|
15
|
+
upgrade_packages.each { |package| apply(package) }
|
16
|
+
puts "\nProject is up-to-date! Shaf version: #{current_version}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def apply(package)
|
21
|
+
package.load.apply or raise UpgradeFailedError
|
22
|
+
write_shaf_version package.version.to_s
|
23
|
+
rescue Errno::ENOENT
|
24
|
+
raise UpgradeFailedError,
|
25
|
+
"Failed to execute system command 'patch'. Make sure that 'patch' installed!" \
|
26
|
+
" (E.g. `sudo apt install patch` for Ubuntu)"
|
27
|
+
end
|
28
|
+
|
29
|
+
def upgrade_packages
|
30
|
+
Shaf::Upgrade::Package.all.select { |package| package > current_version }
|
31
|
+
end
|
32
|
+
|
33
|
+
def current_version
|
34
|
+
read_shaf_version or raise UnknownShafVersionError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -18,12 +18,6 @@ module Shaf
|
|
18
18
|
end
|
19
19
|
|
20
20
|
module Helpers
|
21
|
-
def policy(resource)
|
22
|
-
return @policy if defined?(@policy) && @policy
|
23
|
-
user = current_user if respond_to? :current_user
|
24
|
-
@policy = self.class.policy_class&.new(user, resource)
|
25
|
-
end
|
26
|
-
|
27
21
|
def authorize(action, resource = nil)
|
28
22
|
policy(resource) or raise Authorize::NoPolicyError
|
29
23
|
@policy.public_send method_for(action)
|
@@ -33,6 +27,14 @@ module Shaf
|
|
33
27
|
authorize(action, resource) or raise Authorize::PolicyViolationError
|
34
28
|
end
|
35
29
|
|
30
|
+
private
|
31
|
+
|
32
|
+
def policy(resource)
|
33
|
+
return @policy if defined?(@policy) && @policy
|
34
|
+
user = current_user if respond_to? :current_user
|
35
|
+
@policy = self.class.policy_class&.new(user, resource)
|
36
|
+
end
|
37
|
+
|
36
38
|
def method_for(action)
|
37
39
|
return action if action.to_s.end_with? '?'
|
38
40
|
"#{action}?".to_sym
|
@@ -54,10 +54,53 @@ module Shaf
|
|
54
54
|
# edit_book_uri_template => /books/:id/edit
|
55
55
|
#
|
56
56
|
class CreateUriMethods
|
57
|
+
|
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
|
+
|
57
95
|
def initialize(name, base: nil, plural_name: nil)
|
58
96
|
@name = name.to_s
|
59
97
|
@base = base&.sub(%r(/\Z), '') || ''
|
60
|
-
@plural_name = plural_name&.to_s || name.to_s
|
98
|
+
@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
|
61
104
|
end
|
62
105
|
|
63
106
|
def call
|
@@ -76,88 +119,101 @@ module Shaf
|
|
76
119
|
attr_reader :name, :base, :plural_name
|
77
120
|
|
78
121
|
def register_resources_uri
|
79
|
-
|
80
|
-
helper_name = "#{plural_name}_uri"
|
81
|
-
|
82
|
-
UriHelperMethods.register(helper_name) do |**query|
|
83
|
-
query_string = MethodBuilder.query_string(query)
|
84
|
-
"#{uri}#{query_string}".freeze
|
85
|
-
end
|
86
|
-
UriHelperMethods.register("#{helper_name}_template") { uri.freeze }
|
122
|
+
template_uri = "#{base}/#{plural_name}".freeze
|
123
|
+
helper_name = "#{plural_name}_uri".freeze
|
124
|
+
register(template_uri, helper_name)
|
87
125
|
end
|
88
126
|
|
89
127
|
def register_resource_uri
|
90
|
-
|
91
|
-
helper_name = "#{name}_uri"
|
92
|
-
|
93
|
-
UriHelperMethods.register helper_name do |resrc, **query|
|
94
|
-
id = resrc.is_a?(Integer) ? resrc : resrc&.id
|
95
|
-
query_string = MethodBuilder.query_string(query)
|
96
|
-
|
97
|
-
next "#{uri}/#{id}#{query_string}".freeze if id.is_a?(Integer)
|
98
|
-
raise ArgumentError,
|
99
|
-
"In #{helper_name}: id must be an integer! was #{id.class}"
|
100
|
-
end
|
101
|
-
|
102
|
-
UriHelperMethods.register "#{helper_name}_template" do
|
103
|
-
"#{uri}/:id".freeze
|
104
|
-
end
|
128
|
+
template_uri = "#{base}/#{plural_name}/:id".freeze
|
129
|
+
helper_name = "#{name}_uri".freeze
|
130
|
+
register(template_uri, helper_name)
|
105
131
|
end
|
106
132
|
|
107
133
|
# If a resource has the same singular and plural names, then this method
|
108
134
|
# should be used. It will return the resource uri when a resource is given
|
109
135
|
# as argument and the resources uri when no arguments are provided.
|
110
136
|
def register_resource_uri_by_arg
|
111
|
-
|
137
|
+
resource_template_uri = "#{base}/#{plural_name}/:id"
|
138
|
+
collection_template_uri = "#{base}/#{plural_name}"
|
112
139
|
helper_name = "#{plural_name}_uri"
|
113
140
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
"#{uri}#{query_string}".freeze
|
119
|
-
else
|
120
|
-
id = (resrc.is_a?(Integer) ? resrc : resrc.id).to_i
|
121
|
-
|
122
|
-
next "#{uri}/#{id}#{query_string}".freeze if id.is_a?(Integer)
|
123
|
-
raise ArgumentError,
|
124
|
-
"In #{helper_name}: id must be an integer! was #{id.class}"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
UriHelperMethods.register "#{helper_name}_template" do |collection = false|
|
129
|
-
(collection ? uri : "#{uri}/:id").freeze
|
141
|
+
block = resource_or_collection_method_proc(resource_template_uri, collection_template_uri)
|
142
|
+
UriHelperMethods.register(helper_name, &block)
|
143
|
+
UriHelperMethods.register("#{helper_name}_template") do |collection = false|
|
144
|
+
(collection ? collection_template_uri : resource_template_uri).freeze
|
130
145
|
end
|
131
146
|
end
|
132
147
|
|
133
148
|
def register_new_resource_uri
|
134
|
-
|
135
|
-
helper_name = "new_#{name}_uri"
|
136
|
-
|
137
|
-
UriHelperMethods.register(helper_name) do |**query|
|
138
|
-
query_string = MethodBuilder.query_string(query)
|
139
|
-
"#{uri}#{query_string}".freeze
|
140
|
-
end
|
141
|
-
UriHelperMethods.register("#{helper_name}_template") { uri.freeze }
|
149
|
+
template_uri = "#{base}/#{plural_name}/form".freeze
|
150
|
+
helper_name = "new_#{name}_uri".freeze
|
151
|
+
register(template_uri, helper_name)
|
142
152
|
end
|
143
153
|
|
144
154
|
def register_edit_resource_uri
|
145
|
-
|
146
|
-
helper_name = "edit_#{name}_uri"
|
155
|
+
template_uri = "#{base}/#{plural_name}/:id/edit".freeze
|
156
|
+
helper_name = "edit_#{name}_uri".freeze
|
157
|
+
register(template_uri, helper_name)
|
158
|
+
end
|
147
159
|
|
148
|
-
|
149
|
-
|
150
|
-
|
160
|
+
def register(template_uri, helper_name)
|
161
|
+
block = resource_helper_method_proc(template_uri)
|
162
|
+
UriHelperMethods.register(helper_name, &block)
|
163
|
+
UriHelperMethods.register("#{helper_name}_template") { template_uri }
|
164
|
+
end
|
165
|
+
|
166
|
+
def resource_helper_method_proc(template_uri)
|
167
|
+
arg_count = MethodBuilder.extract_symbols(template_uri).size
|
151
168
|
|
152
|
-
|
153
|
-
|
154
|
-
|
169
|
+
case arg_count
|
170
|
+
when 0
|
171
|
+
->(**query) do
|
172
|
+
CreateUriMethods.resource_helper_uri(template_uri, query)
|
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}"
|
155
185
|
end
|
186
|
+
end
|
156
187
|
|
157
|
-
|
158
|
-
|
188
|
+
def resource_or_collection_method_proc(resource_template_uri, collection_template_uri)
|
189
|
+
case nesting_depth
|
190
|
+
when 0
|
191
|
+
->(resrc = nil, **query) {
|
192
|
+
args = if resrc.nil?
|
193
|
+
[collection_template_uri, query]
|
194
|
+
else
|
195
|
+
[resource_template_uri, resrc, query]
|
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
|
+
}
|
208
|
+
else
|
209
|
+
raise UriTemplateNestingError,
|
210
|
+
"Too deep nesting level (max #{MAX_NESTING_DEPTH}): #{template_uri}"
|
159
211
|
end
|
160
212
|
end
|
213
|
+
|
214
|
+
def nesting_depth
|
215
|
+
MethodBuilder.extract_symbols(base).size
|
216
|
+
end
|
161
217
|
end
|
162
218
|
|
163
219
|
module MethodBuilder
|
@@ -191,24 +247,26 @@ module Shaf
|
|
191
247
|
Ruby
|
192
248
|
end
|
193
249
|
|
194
|
-
private
|
195
|
-
|
196
250
|
def extract_symbols(uri)
|
197
251
|
uri.split('/').grep(/:.*/).map { |t| t[1..-1] }.map(&:to_sym)
|
198
252
|
end
|
199
253
|
|
254
|
+
def transform_symbols(uri)
|
255
|
+
uri.split('/').map do |segment|
|
256
|
+
next segment unless segment.start_with? ':'
|
257
|
+
yield segment
|
258
|
+
end.join('/')
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
262
|
+
|
200
263
|
def interpolated_uri_string(uri)
|
201
|
-
return uri if uri
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
"\#{#{str}.respond_to?(#{segment}) ? #{str}.#{str} : #{str}}"
|
207
|
-
else
|
208
|
-
segment
|
209
|
-
end
|
264
|
+
return uri if uri == '/'
|
265
|
+
|
266
|
+
transform_symbols(uri) do |segment|
|
267
|
+
str = segment[1..-1]
|
268
|
+
"\#{#{str}.respond_to?(#{segment}) ? #{str}.#{str} : #{str}}"
|
210
269
|
end
|
211
|
-
segments.join('/')
|
212
270
|
end
|
213
271
|
end
|
214
272
|
end
|