shaf 0.8.0 → 1.0.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.
Files changed (56) 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 +19 -4
  5. data/lib/shaf.rb +1 -0
  6. data/lib/shaf/app.rb +7 -10
  7. data/lib/shaf/command/server.rb +4 -2
  8. data/lib/shaf/errors.rb +22 -2
  9. data/lib/shaf/extensions/controller_hooks.rb +14 -0
  10. data/lib/shaf/extensions/resource_uris.rb +1 -1
  11. data/lib/shaf/formable/field.rb +10 -6
  12. data/lib/shaf/formable/form.rb +4 -0
  13. data/lib/shaf/generator/base.rb +2 -2
  14. data/lib/shaf/generator/controller.rb +11 -11
  15. data/lib/shaf/generator/helper.rb +33 -0
  16. data/lib/shaf/generator/migration.rb +6 -1
  17. data/lib/shaf/generator/migration/empty.rb +2 -2
  18. data/lib/shaf/generator/serializer.rb +4 -4
  19. data/lib/shaf/generator/templates/api/controller.rb.erb +4 -3
  20. data/lib/shaf/generator/templates/api/serializer.rb.erb +10 -12
  21. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +4 -2
  22. data/lib/shaf/helpers.rb +2 -0
  23. data/lib/shaf/helpers/http_header.rb +26 -0
  24. data/lib/shaf/helpers/paginate.rb +4 -2
  25. data/lib/shaf/helpers/payload.rb +33 -8
  26. data/lib/shaf/middleware/request_id.rb +3 -1
  27. data/lib/shaf/rake/db.rb +5 -5
  28. data/lib/shaf/rake/test.rb +0 -27
  29. data/lib/shaf/router.rb +133 -0
  30. data/lib/shaf/settings.rb +36 -11
  31. data/lib/shaf/spec/http_method_utils.rb +11 -3
  32. data/lib/shaf/spec/integration_spec.rb +4 -4
  33. data/lib/shaf/spec/serializer_spec.rb +1 -1
  34. data/lib/shaf/upgrade/manifest.rb +34 -9
  35. data/lib/shaf/upgrade/package.rb +24 -15
  36. data/lib/shaf/utils.rb +31 -21
  37. data/lib/shaf/version.rb +1 -1
  38. data/templates/Rakefile +27 -0
  39. data/templates/api/controllers/base_controller.rb +13 -8
  40. data/templates/api/serializers/error_serializer.rb +3 -1
  41. data/templates/api/serializers/form_serializer.rb +11 -16
  42. data/templates/api/serializers/validation_error_serializer.rb +13 -0
  43. data/templates/config.ru +1 -1
  44. data/templates/config/bootstrap.rb +3 -1
  45. data/templates/config/database.rb +6 -7
  46. data/templates/config/directories.rb +3 -2
  47. data/templates/config/helpers.rb +1 -1
  48. data/templates/config/initializers/db_migrations.rb +14 -8
  49. data/templates/config/initializers/logging.rb +2 -2
  50. data/templates/config/initializers/sequel.rb +1 -0
  51. data/templates/config/paths.rb +7 -0
  52. data/templates/config/settings.yml +5 -0
  53. data/upgrades/1.0.0.tar.gz +0 -0
  54. metadata +38 -19
  55. metadata.gz.sig +0 -0
  56. data/templates/config/constants.rb +0 -8
@@ -3,10 +3,18 @@ module Shaf
3
3
  module HttpUtils
4
4
  include ::Rack::Test::Methods
5
5
 
6
- [:get, :put, :patch, :post, :delete, :options, :head, :link, :unlink].each do |m|
7
- define_method m do |*args|
6
+ %i[get put patch post delete options head link unlink].each do |m|
7
+ define_method m do |uri, payload = nil, options = {}|
8
8
  set_headers
9
- super(*args)
9
+
10
+ if payload
11
+ payload = JSON.generate(payload) if payload.respond_to? :to_h
12
+ options['CONTENT_TYPE'] ||= 'application/json'
13
+ super(uri, payload, options)
14
+ else
15
+ super(uri, options)
16
+ end
17
+
10
18
  set_payload parse_response(last_response.body)
11
19
  end
12
20
  end
@@ -6,14 +6,14 @@ module Shaf
6
6
  include PayloadUtils
7
7
  include HttpUtils
8
8
 
9
- register_spec_type self do |desc, args|
10
- next unless args && args.is_a?(Hash)
9
+ register_spec_type self do |_desc, args|
10
+ next unless args&.is_a?(Hash)
11
11
  args[:type]&.to_s == 'integration'
12
12
  end
13
13
 
14
14
  def set_headers
15
15
  if defined?(@__integration_test_auth_token) && @__integration_test_auth_token
16
- header 'X-AUTH-TOKEN', @__integration_test_auth_token
16
+ header Settings.auth_token_header, @__integration_test_auth_token
17
17
  end
18
18
  end
19
19
 
@@ -25,7 +25,7 @@ module Shaf
25
25
  end
26
26
 
27
27
  def app
28
- App.instance
28
+ App.app
29
29
  end
30
30
 
31
31
  def follow_rel(rel, method: nil)
@@ -11,7 +11,7 @@ module Shaf
11
11
  args[:type]&.to_s == 'serializer'
12
12
  end
13
13
 
14
- def serialize(resource, current_user:)
14
+ def serialize(resource, current_user: nil)
15
15
  serializer = __serializer || HALPresenter
16
16
  set_payload serializer.to_hal(resource, current_user: current_user)
17
17
  end
@@ -14,23 +14,48 @@ module Shaf
14
14
  @files[:regexp] = build_patterns(params[:substitutes])
15
15
  end
16
16
 
17
- def patch_for(file)
18
- files[:patch].select { |_, pattern| file =~ pattern }.keys
17
+ def patches
18
+ files[:patch]
19
19
  end
20
20
 
21
- def regexp_for(file)
22
- files[:regexp].select { |_, pattern| file =~ pattern }.keys
21
+ def additions
22
+ files[:add]
23
+ end
24
+
25
+ def removals
26
+ files[:drop]
27
+ end
28
+
29
+ def regexps
30
+ files[:regexp]
31
+ end
32
+
33
+ def patches_for(file)
34
+ patches.select { |_, pattern| file =~ pattern }.keys
35
+ end
36
+
37
+ def regexps_for(file)
38
+ regexps.select { |_, pattern| file =~ pattern }.keys
23
39
  end
24
40
 
25
41
  def drop?(file)
26
- files[:drop].any? { |pattern| file =~ pattern }
42
+ removals.any? { |pattern| file =~ pattern }
27
43
  end
28
44
 
29
45
  def stats
30
- "Add: #{files[:add].size}, " \
31
- "Del: #{files[:drop].size}, " \
32
- "Patch: #{files[:patch].size}, " \
33
- "Regexp: #{files[:regexp].size}"
46
+ {
47
+ additions: additions.size,
48
+ removals: removals.size,
49
+ patches: patches.size,
50
+ regexps: regexps.size
51
+ }
52
+ end
53
+
54
+ def stats_str
55
+ "Add: #{additions.size}, " \
56
+ "Del: #{removals.size}, " \
57
+ "Patch: #{patches.size}, " \
58
+ "Regexp: #{regexps.size}"
34
59
  end
35
60
 
36
61
  private
@@ -77,15 +77,15 @@ module Shaf
77
77
 
78
78
  def apply(dir = nil)
79
79
  apply_patches(dir)
80
- apply_additions
81
80
  apply_drops(dir)
81
+ apply_additions
82
82
  apply_substitutes(dir)
83
83
  end
84
84
 
85
85
  def to_s
86
86
  str = "Shaf::Upgrade::Package for version #{@version}"
87
87
  return str if @manifest.nil?
88
- "#{str} (#{@manifest.stats})"
88
+ "#{str} (#{@manifest.stats_str})"
89
89
  end
90
90
 
91
91
  private
@@ -143,8 +143,8 @@ module Shaf
143
143
  end
144
144
 
145
145
  def apply_patches(dir = nil)
146
- files_in(dir).all? do |file|
147
- @manifest.patch_for(file).all? do |name|
146
+ files_in(dir).each do |file|
147
+ @manifest.patches_for(file).each do |name|
148
148
  patch = @files[name]
149
149
  apply_patch(file, patch)
150
150
  end
@@ -152,35 +152,40 @@ module Shaf
152
152
  end
153
153
 
154
154
  def apply_patch(file, patch)
155
- success = nil
156
- Open3.popen3('patch', file, '-r', '-') do |i, o, e, t|
155
+ Open3.popen3('patch', file) do |i, o, e, t|
157
156
  i.write patch
158
157
  i.close
159
158
  puts o.read
160
159
  err = e.read
161
160
  puts err unless err.empty?
162
- success = t.value.success?
161
+ next if t.value.success?
162
+ STDERR.puts "Failed to apply patch for: #{file}\n"
163
163
  end
164
- success
165
164
  end
166
165
 
167
166
  def apply_additions
168
- @manifest.files[:add].each do |chksum, filename|
167
+ puts '' unless @manifest.additions.empty?
168
+ @manifest.additions.each do |chksum, filename|
169
169
  content = @files[chksum]
170
170
  FileUtils.mkdir_p File.dirname(filename)
171
+ puts "adding file: #{filename}"
171
172
  File.open(filename, 'w') { |file| file.write(content) }
172
173
  end
173
174
  end
174
175
 
175
176
  def apply_drops(dir = nil)
177
+ puts '' unless @manifest.removals.empty?
176
178
  files_in(dir).map do |file|
177
- File.unlink(file) if @manifest.drop?(file)
179
+ next unless @manifest.drop?(file)
180
+ puts "removing file: #{file}"
181
+ File.unlink(file)
178
182
  end
179
183
  end
180
184
 
181
185
  def apply_substitutes(dir = nil)
186
+ puts '' unless @manifest.regexps.empty?
182
187
  files_in(dir).all? do |file|
183
- @manifest.regexp_for(file).all? do |name|
188
+ @manifest.regexps_for(file).all? do |name|
184
189
  params = symbolize_keys(YAML.safe_load(@files[name]))
185
190
  apply_substitute(file, params)
186
191
  end
@@ -188,18 +193,22 @@ module Shaf
188
193
  end
189
194
 
190
195
  def apply_substitute(file, params)
191
- pattern = params[:pattern] or return
192
- replacement = params[:replace] or return
196
+ return unless params[:pattern] && params[:replace]
197
+
198
+ pattern = Regexp.new(params[:pattern])
199
+ replacement = params[:replace]
200
+
193
201
  tmp = Tempfile.open do |new_file|
194
202
  File.readlines(file).each do |line|
195
- new_file << line.sub(Regexp.new(pattern), replacement)
203
+ new_file << line.gsub(pattern, replacement)
196
204
  end
197
205
  new_file
198
206
  end
207
+
199
208
  FileUtils.mv(tmp.path, file)
200
209
  end
201
210
 
202
- # Refactor this when support for ruby < 2.5 is dropped
211
+ # Refactor this when support for ruby 2.4 is dropped
203
212
  def symbolize_keys(hash)
204
213
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
205
214
  end
@@ -1,39 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
1
4
  require 'shaf/version'
2
5
 
3
6
  module Shaf
4
7
  module Utils
8
+ extend Forwardable
9
+
5
10
  class ProjectRootNotFound < StandardError; end
6
11
 
7
12
  SHAF_VERSION_FILE = '.shaf'.freeze
8
13
 
9
- # FIXME!!!
10
- def self.pluralize(noun)
11
- noun + 's' # FIXME!!
12
- end
14
+ class << self
15
+ def model_name(name)
16
+ name.capitalize.gsub(/[_-](\w)/) { $1.upcase }
17
+ end
13
18
 
14
- def self.model_name(name)
15
- name.capitalize.gsub(/[_-](\w)/) { $1.upcase }
16
- end
19
+ def gem_root
20
+ File.expand_path('../..', __dir__)
21
+ end
17
22
 
18
- def self.gem_root
19
- File.expand_path('../../..', __FILE__)
20
- end
23
+ # FIXME!!!
24
+ def pluralize(noun)
25
+ return pluralize(noun.to_s).to_sym if noun.is_a? Symbol
26
+ noun + 's'
27
+ end
21
28
 
22
- # FIXME!!!
23
- def self.singularize(noun)
24
- return singularize(noun.to_s).to_sym if noun.is_a? Symbol
25
- return noun unless noun.end_with? 's'
26
- noun[0..-2]
27
- end
29
+ # FIXME!!!
30
+ def singularize(noun)
31
+ return singularize(noun.to_s).to_sym if noun.is_a? Symbol
32
+ return noun unless noun.end_with? 's'
33
+ noun[0..-2]
34
+ end
28
35
 
29
- def pluralize(noun)
30
- Utils::pluralize(noun)
31
- end
36
+ def symbolize(str)
37
+ :"#{str}"
38
+ end
32
39
 
33
- def gem_root
34
- self.class.gem_root
40
+ def symbol_string(str)
41
+ symbolize(str).inspect
42
+ end
35
43
  end
36
44
 
45
+ def_delegators Utils, :pluralize, :singularize, :symbolize, :symbol_string, :gem_root
46
+
37
47
  def project_root
38
48
  return @project_root if defined? @project_root
39
49
  dir = Dir.pwd
@@ -1,3 +1,3 @@
1
1
  module Shaf
2
- VERSION = "0.8.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -10,3 +10,30 @@ Shaf::ApiDocTask.new do |api_doc|
10
10
  api_doc.html_output_dir = File.join(Shaf::Settings.public_folder, "doc")
11
11
  api_doc.yaml_output_dir = Shaf::Settings.documents_dir || "doc/api"
12
12
  end
13
+
14
+ namespace :test do
15
+ Shaf::TestTask.new(:integration) do |t|
16
+ t.pattern = "spec/integration/**/*_spec.rb"
17
+ end
18
+
19
+ Shaf::TestTask.new(:models) do |t|
20
+ t.pattern = "spec/models/**/*_spec.rb"
21
+ end
22
+
23
+ Shaf::TestTask.new(:serializers) do |t|
24
+ t.pattern = "spec/serializers/**/*_spec.rb"
25
+ end
26
+
27
+ Shaf::TestTask.new(:lib) do |t|
28
+ t.pattern = "spec/lib/**/*_spec.rb"
29
+ end
30
+
31
+ Shaf::TestTask.new(:all) do |t|
32
+ t.pattern = [
33
+ "spec/lib/**/*_spec.rb",
34
+ "spec/models/**/*_spec.rb",
35
+ "spec/serializers/**/*_spec.rb",
36
+ "spec/integration/**/*_spec.rb"
37
+ ]
38
+ end
39
+ end
@@ -7,20 +7,22 @@ class BaseController < Sinatra::Base
7
7
  enable :logging
8
8
  enable :method_override
9
9
  mime_type :hal, 'application/hal+json'
10
- set :views, VIEWS_DIR
10
+ set :views, Shaf::Settings.views_folder
11
11
  set :static, !production?
12
- set :public_folder, ASSETS_DIR
12
+ set :public_folder, Shaf::Settings.public_folder
13
13
  disable :dump_errors
14
14
  set :show_exceptions, :after_handler
15
15
  enable :current_user
16
- set :auth_token_header, AUTH_TOKEN_HEADER
16
+ set :auth_token_header, Shaf::Settings.auth_token_header
17
17
  end
18
18
 
19
19
  use Rack::Deflater
20
20
 
21
+ Shaf::Router.mount(self, default: true)
22
+
21
23
  def self.inherited(controller)
22
24
  super
23
- Shaf::App.use controller
25
+ Shaf::Router.mount controller
24
26
  end
25
27
 
26
28
  def self.log
@@ -39,16 +41,19 @@ class BaseController < Sinatra::Base
39
41
  log.debug "Payload: #{payload || 'empty'}"
40
42
  end
41
43
 
44
+ not_found do
45
+ err = NotFoundError.new "Resource \"#{request.path_info}\" does not exist"
46
+ respond_with(err, status: err.http_status)
47
+ end
48
+
42
49
  error StandardError do
43
50
  err = env['sinatra.error']
44
51
  log.error err.message
45
- err.backtrace.each(&log.method(:error))
52
+ Array(err.backtrace).each(&log.method(:error))
46
53
 
47
54
  api_error = to_api_error(err)
48
55
 
49
- respond_with api_error,
50
- status: api_error.http_status,
51
- serializer: ErrorSerializer
56
+ respond_with(api_error, status: api_error.http_status)
52
57
  end
53
58
 
54
59
  def to_api_error(err)
@@ -1,11 +1,13 @@
1
1
  require 'serializers/base_serializer'
2
2
 
3
3
  class ErrorSerializer < BaseSerializer
4
-
5
4
  model Shaf::Errors::ServerError
6
5
 
7
6
  attribute :title
8
7
  attribute :code
9
8
  attribute :message
10
9
 
10
+ link :profile do
11
+ Shaf::Settings.error_profile_uri
12
+ end
11
13
  end
@@ -9,37 +9,32 @@ class FormSerializer < BaseSerializer
9
9
  (options[:method] || resource&.method || 'POST').to_s.upcase
10
10
  end
11
11
 
12
- attribute :name do
13
- options[:name] || resource&.name
14
- end
15
-
16
- attribute :title do
17
- options[:title] || resource&.title
18
- end
19
-
20
- attribute :href do
21
- options[:href] || resource&.href
22
- end
23
-
24
- attribute :type do
25
- options[:type] || resource&.type
12
+ %i[name title href type submit].each do |sym|
13
+ attribute sym do
14
+ options[sym] || resource&.public_send(sym)
15
+ end
26
16
  end
27
17
 
28
18
  link :self do
29
19
  options[:self_link] || resource&.self_link
30
20
  end
31
21
 
22
+ link :profile do
23
+ Shaf::Settings.form_profile_uri
24
+ end
25
+
32
26
  post_serialize do |hash|
33
27
  fields = resource&.fields
34
28
  break if fields.nil? || fields.empty?
35
29
  hash[:fields] = fields.map do |field|
36
30
  {
37
31
  name: field.name,
38
- type: field.type,
39
- label: field.label,
32
+ type: field.type
40
33
  }.tap do |f|
34
+ f[:title] = field.title if field.title
41
35
  f[:value] = field.value if field.has_value?
42
36
  f[:required] = true if field.required
37
+ f[:hidden] = true if field.hidden
43
38
  end
44
39
  end
45
40
  end
@@ -0,0 +1,13 @@
1
+ class ValidationErrorSerializer < BaseSerializer
2
+ model Shaf::Errors::ValidationError
3
+
4
+ attribute :title
5
+ attribute :code
6
+ attribute :fields do
7
+ resource.fields
8
+ end
9
+
10
+ link :profile do
11
+ Shaf::Settings.error_profile_uri
12
+ end
13
+ end