shaf 0.3.1 → 0.4.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 +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
|