shaf 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/bin/shaf +3 -0
  5. data/lib/shaf.rb +1 -0
  6. data/lib/shaf/api_doc/document.rb +0 -1
  7. data/lib/shaf/app.rb +1 -1
  8. data/lib/shaf/command.rb +19 -2
  9. data/lib/shaf/command/generate.rb +11 -1
  10. data/lib/shaf/command/new.rb +1 -6
  11. data/lib/shaf/command/server.rb +7 -0
  12. data/lib/shaf/command/upgrade.rb +38 -0
  13. data/lib/shaf/extensions/authorize.rb +8 -6
  14. data/lib/shaf/extensions/resource_uris.rb +129 -71
  15. data/lib/shaf/generator.rb +4 -0
  16. data/lib/shaf/generator/controller.rb +2 -2
  17. data/lib/shaf/generator/migration.rb +33 -14
  18. data/lib/shaf/generator/migration/add_column.rb +1 -3
  19. data/lib/shaf/generator/migration/add_index.rb +42 -0
  20. data/lib/shaf/generator/model.rb +4 -4
  21. data/lib/shaf/generator/policy.rb +1 -1
  22. data/lib/shaf/generator/scaffold.rb +3 -3
  23. data/lib/shaf/generator/serializer.rb +5 -5
  24. data/lib/shaf/rake.rb +5 -0
  25. data/lib/shaf/rake/db.rb +79 -0
  26. data/lib/shaf/rake/test.rb +32 -0
  27. data/lib/shaf/registrable_factory.rb +8 -3
  28. data/lib/shaf/settings.rb +20 -4
  29. data/lib/shaf/tasks.rb +6 -3
  30. data/lib/shaf/{api_doc/task.rb → tasks/api_doc_task.rb} +6 -5
  31. data/lib/shaf/tasks/db_task.rb +42 -0
  32. data/lib/shaf/tasks/test_task.rb +16 -0
  33. data/lib/shaf/upgrade.rb +3 -0
  34. data/lib/shaf/upgrade/manifest.rb +31 -0
  35. data/lib/shaf/upgrade/package.rb +158 -0
  36. data/lib/shaf/upgrade/version.rb +52 -0
  37. data/lib/shaf/utils.rb +30 -0
  38. data/lib/shaf/version.rb +1 -1
  39. data/templates/Rakefile +2 -3
  40. data/templates/config/database.rb +3 -3
  41. metadata +33 -202
  42. metadata.gz.sig +0 -0
  43. data/lib/shaf/api_doc.rb +0 -3
  44. data/lib/shaf/tasks/db.rb +0 -81
  45. data/lib/shaf/tasks/test.rb +0 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d610f6b52c0b849399c31536099eb1aecfde8b0907ee7bb1839b3430b0ccdb12
4
- data.tar.gz: '069c31284757c68cf40538cbf623e3767847420b67cef09084f7461360978508'
3
+ metadata.gz: 749585960501bd61a7e7f4f22c2a90c2108f86c6cf0698ec87f495bb78d87389
4
+ data.tar.gz: da5fc9e7f87876f12654d5ea8473e7f0cd4a9dc3daba88055a51f7aaf0a794a5
5
5
  SHA512:
6
- metadata.gz: b0b6fa7cf8e841b6791bfe2aa271ce3cb752c8efd603299d56c999b8b86df0d62ff03bd8b8cf00ab215bdaab17178b8ced05af25da5f7298bd998b79c404eed4
7
- data.tar.gz: dedfaac82ee3fb083733354d41e4fb1417484868afb8ab956e072bf436b6035ac02287abb3b5a82d69af77b6d91da47e6eade4463dd13f1ef1c7ff98e72a429d
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
@@ -7,3 +7,4 @@ require 'shaf/formable'
7
7
  require 'shaf/extensions'
8
8
  require 'shaf/helpers'
9
9
  require 'shaf/doc_model'
10
+ require 'shaf/upgrade'
@@ -2,7 +2,6 @@ require 'fileutils'
2
2
  require 'yaml'
3
3
  require 'redcarpet'
4
4
  require 'redcarpet/render_strip'
5
- require 'shaf/api_doc/comment'
6
5
 
7
6
  module Shaf
8
7
  module ApiDoc
data/lib/shaf/app.rb CHANGED
@@ -10,7 +10,7 @@ module Shaf
10
10
 
11
11
  def create_instance
12
12
  @instance = Sinatra.new
13
- @instance.set :port, Shaf::Settings.port || 3000
13
+ @instance.set :port, Settings.port || 3000
14
14
  @instance.use Shaf::Middleware::RequestId
15
15
  end
16
16
 
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 ArgumentError < StandardError; end
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
@@ -21,7 +21,7 @@ module Shaf
21
21
  Dir.chdir(@project_name) do
22
22
  copy_templates
23
23
  create_gemfile
24
- create_shaf_version_file
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
@@ -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 + '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
- uri = "#{base}/#{plural_name}"
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
- uri = "#{base}/#{plural_name}"
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
- uri = "#{base}/#{plural_name}"
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
- UriHelperMethods.register helper_name do |resrc = nil, **query|
115
- query_string = MethodBuilder.query_string(query)
116
-
117
- if resrc.nil?
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
- uri = "#{base}/#{plural_name}/form"
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
- uri = "#{base}/#{plural_name}"
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
- UriHelperMethods.register helper_name do |resrc, **query|
149
- id = resrc.is_a?(Integer) ? resrc : resrc&.id
150
- query_string = MethodBuilder.query_string(query)
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
- next "#{uri}/#{id}/edit#{query_string}".freeze if id.is_a?(Integer)
153
- raise ArgumentError,
154
- "In #{helper_name}: id must be an integer! was #{id.class}"
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
- UriHelperMethods.register "#{helper_name}_template" do
158
- "#{uri}/:id/edit".freeze
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.split('/').empty?
202
-
203
- segments = uri.split('/').map do |segment|
204
- if segment.start_with? ':'
205
- str = segment[1..-1]
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