shaf 1.0.4 → 1.1.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/lib/shaf/command.rb +4 -3
- data/lib/shaf/command/base.rb +6 -1
- data/lib/shaf/command/test.rb +134 -0
- data/lib/shaf/command/test/filter.rb +23 -0
- data/lib/shaf/command/test/runnable_method.rb +22 -0
- data/lib/shaf/command/test/runner.rb +25 -0
- data/lib/shaf/errors.rb +1 -1
- data/lib/shaf/extensions/authorize.rb +1 -2
- data/lib/shaf/extensions/current_user.rb +1 -1
- data/lib/shaf/formable.rb +37 -12
- data/lib/shaf/generator.rb +1 -0
- data/lib/shaf/generator/base.rb +6 -4
- data/lib/shaf/generator/forms.rb +68 -0
- data/lib/shaf/generator/model.rb +10 -25
- data/lib/shaf/generator/serializer.rb +5 -5
- data/lib/shaf/generator/templates/api/forms.rb.erb +19 -0
- data/lib/shaf/generator/templates/api/model.rb.erb +0 -17
- data/lib/shaf/helpers/payload.rb +28 -37
- data/lib/shaf/settings.rb +3 -6
- data/lib/shaf/spec/payload_utils.rb +18 -14
- data/lib/shaf/upgrade/package.rb +2 -6
- data/lib/shaf/utils.rb +23 -3
- data/lib/shaf/version.rb +1 -1
- data/templates/api/serializers/error_serializer.rb +1 -0
- data/templates/api/serializers/form_serializer.rb +2 -2
- data/templates/api/serializers/validation_error_serializer.rb +1 -0
- data/templates/config/paths.rb +1 -0
- data/upgrades/1.1.0.tar.gz +0 -0
- metadata +10 -3
- metadata.gz.sig +1 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f71cd9b7d938bb9cf8c6cabb87dc85bd912052e371884ac253ec20afe8657c90
|
4
|
+
data.tar.gz: e09e82d1f026f3d00e9a849b7ff65c04479940838f0f45bbdfa644ecdc47ef81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4140819946300e345ad2898212241275017c57f586c5dc47d51872047e2a9d4dd48e2d5e06662e83b98ba8963b6c7e87d69d7cae0fe4e79b11ecf0639f739d42
|
7
|
+
data.tar.gz: 3a75893536dd2a17edcdacb8aaba291baa0ecbebc33d2b16b79015d80f922de0f3af79264f87b8acdf0c5b98f6285c6289d2e1e9db419e62cc7af5341620d7e0
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/lib/shaf/command.rb
CHANGED
@@ -11,9 +11,10 @@ module Shaf
|
|
11
11
|
end
|
12
12
|
|
13
13
|
require 'shaf/command/base'
|
14
|
-
require 'shaf/command/new'
|
15
|
-
require 'shaf/command/upgrade'
|
16
|
-
require 'shaf/command/server'
|
17
14
|
require 'shaf/command/console'
|
18
15
|
require 'shaf/command/generate'
|
16
|
+
require 'shaf/command/new'
|
17
|
+
require 'shaf/command/server'
|
18
|
+
require 'shaf/command/test'
|
19
|
+
require 'shaf/command/upgrade'
|
19
20
|
require 'shaf/command/version'
|
data/lib/shaf/command/base.rb
CHANGED
@@ -41,11 +41,16 @@ module Shaf
|
|
41
41
|
|
42
42
|
def parse_options!
|
43
43
|
parser = OptionParser.new
|
44
|
-
|
44
|
+
common_options(parser, options)
|
45
|
+
self.class.options(parser, options)
|
45
46
|
parser.parse!(args)
|
46
47
|
rescue OptionParser::InvalidOption => e
|
47
48
|
raise ArgumentError, e.message
|
48
49
|
end
|
50
|
+
|
51
|
+
def common_options(parser, _options)
|
52
|
+
parser.on('--trace', 'Show backtrace on command failure')
|
53
|
+
end
|
49
54
|
end
|
50
55
|
end
|
51
56
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'set'
|
4
|
+
Bundler.require :development, :test
|
5
|
+
require 'shaf/command/test/filter'
|
6
|
+
require 'shaf/command/test/runner'
|
7
|
+
require 'shaf/command/test/runnable_method'
|
8
|
+
|
9
|
+
module Shaf
|
10
|
+
module Command
|
11
|
+
class Test < Base
|
12
|
+
identifier %r{\A(t(est)?|spec)\Z}
|
13
|
+
usage 'test [FILTER] [..]'
|
14
|
+
|
15
|
+
def call
|
16
|
+
disable_autorun
|
17
|
+
|
18
|
+
bootstrap(env: 'test') do
|
19
|
+
setup_loadpath
|
20
|
+
reporter.start
|
21
|
+
|
22
|
+
spec_files.each do |file|
|
23
|
+
lines = lines_to_run(file)
|
24
|
+
run(file, lines) if lines
|
25
|
+
end
|
26
|
+
|
27
|
+
reporter.report
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def setup_loadpath
|
34
|
+
$LOAD_PATH.unshift(spec_dir) unless $LOAD_PATH.include?(spec_dir)
|
35
|
+
end
|
36
|
+
|
37
|
+
def disable_autorun
|
38
|
+
# This makes sure this at_exit handler is registered after minitest/autorun
|
39
|
+
# This will make it run before the at_exit handler in minitest/autorun
|
40
|
+
require 'minitest/autorun'
|
41
|
+
at_exit do
|
42
|
+
exit! reporter.passed?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def spec_dir
|
47
|
+
@spec_dir ||= Settings.spec_dir || 'spec'
|
48
|
+
end
|
49
|
+
|
50
|
+
def spec_files
|
51
|
+
Dir["#{spec_dir}/**/*.rb"]
|
52
|
+
end
|
53
|
+
|
54
|
+
def filters
|
55
|
+
@filters ||=
|
56
|
+
if args.empty?
|
57
|
+
[Filter::None]
|
58
|
+
else
|
59
|
+
args.map { |arg| Filter.new(arg) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def lines_to_run(file)
|
64
|
+
lines = filters
|
65
|
+
.select { |f| f.match? file }
|
66
|
+
.map(&:lines)
|
67
|
+
|
68
|
+
return if lines.empty?
|
69
|
+
return [] if lines.any?(&:empty?)
|
70
|
+
lines.flatten.to_set
|
71
|
+
end
|
72
|
+
|
73
|
+
def run(file, lines = [])
|
74
|
+
runners(file, lines).each do |runner|
|
75
|
+
runner.call(reporter)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def reporter
|
80
|
+
@reporter ||= Minitest::CompositeReporter.new.tap do |reporter|
|
81
|
+
reporter << Minitest::SummaryReporter.new($stdout)
|
82
|
+
reporter << Minitest::ProgressReporter.new($stdout)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def runners(file, lines)
|
87
|
+
runnables = runnables_in(file)
|
88
|
+
|
89
|
+
return runnables.map { |r| Runner.new r } if lines.empty?
|
90
|
+
|
91
|
+
methods = methods_for(runnables)
|
92
|
+
|
93
|
+
lines.each_with_object([]) do |line, runners|
|
94
|
+
if methods.empty? || line < methods.first.line
|
95
|
+
runnables.map { |r| runners << Runner.new(r) }
|
96
|
+
else
|
97
|
+
spec = methods.partition { |m| m.line < line }.first.last or next
|
98
|
+
runners << Runner.new(spec.runnable, spec.name)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def runnables_in(file)
|
104
|
+
require file
|
105
|
+
|
106
|
+
@runnables ||= Set.new
|
107
|
+
|
108
|
+
Minitest::Runnable.runnables.each_with_object([]) do |runnable, loaded|
|
109
|
+
next unless runnable.runnable_methods.any?
|
110
|
+
next if @runnables.include? runnable
|
111
|
+
@runnables << runnable
|
112
|
+
loaded << runnable
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def methods_for(runnables)
|
117
|
+
methods = []
|
118
|
+
|
119
|
+
runnables.each do |runnable|
|
120
|
+
runnable.runnable_methods.each do |name|
|
121
|
+
methods << RunnableMethod.from(runnable, name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
methods.sort_by { |m| m.line }
|
126
|
+
end
|
127
|
+
|
128
|
+
def relative_to_root(file)
|
129
|
+
file = File.expand_path(file)
|
130
|
+
file.sub(File.join(Settings.app_root, ''), '')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Command
|
5
|
+
class Test < Base
|
6
|
+
class Filter
|
7
|
+
attr_reader :pattern, :lines
|
8
|
+
|
9
|
+
def initialize(criteria)
|
10
|
+
pattern, *lines = criteria.split(':')
|
11
|
+
@lines = lines.map(&:to_i)
|
12
|
+
@pattern = Regexp.new(pattern)
|
13
|
+
end
|
14
|
+
|
15
|
+
def match?(file)
|
16
|
+
pattern.match? file
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Filter::None = Filter.new('.*.rb')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Command
|
5
|
+
class Test < Base
|
6
|
+
class RunnableMethod
|
7
|
+
attr_reader :runnable, :name, :line
|
8
|
+
|
9
|
+
def self.from(runnable, method_name)
|
10
|
+
_, line = runnable.instance_method(method_name).source_location
|
11
|
+
new(runnable, method_name, line)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(runnable, name, line)
|
15
|
+
@runnable = runnable
|
16
|
+
@name = name
|
17
|
+
@line = line
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Shaf
|
3
|
+
module Command
|
4
|
+
class Test < Base
|
5
|
+
class Runner
|
6
|
+
attr_reader :runnable, :method_name
|
7
|
+
|
8
|
+
def initialize(runnable, method_name = nil)
|
9
|
+
@runnable = runnable
|
10
|
+
@method_name = method_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(reporter)
|
14
|
+
runnable.run(reporter, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def options
|
18
|
+
return {} unless method_name
|
19
|
+
|
20
|
+
{filter: method_name}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/shaf/errors.rb
CHANGED
data/lib/shaf/formable.rb
CHANGED
@@ -2,22 +2,47 @@ require 'shaf/formable/builder'
|
|
2
2
|
|
3
3
|
module Shaf
|
4
4
|
module Formable
|
5
|
+
def self.add_class_reader(clazz, name, form)
|
6
|
+
clazz.define_singleton_method(name) { form }
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.add_instance_reader(clazz, name, form, prefill)
|
10
|
+
clazz.define_method(name) do
|
11
|
+
form.tap do |f|
|
12
|
+
f.resource = self
|
13
|
+
f.fill! if prefill
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Deprecated legacy way of specifying forms inside models
|
5
19
|
def form(&block)
|
20
|
+
forms_for(self, &block)
|
21
|
+
return unless defined? $logger
|
22
|
+
|
23
|
+
$logger.info <<~MSG
|
24
|
+
|
25
|
+
|
26
|
+
DEPRECATED method ::form in #{self}
|
27
|
+
Declare forms in a separate class extending Shaf::Formable with the class method forms_for!
|
28
|
+
MSG
|
29
|
+
end
|
30
|
+
|
31
|
+
# New way of writing forms in a separate class/file
|
32
|
+
def forms_for(clazz, &block)
|
6
33
|
builder = Formable::Builder.new(&block)
|
7
|
-
builder.forms.each do |
|
8
|
-
next unless
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
fm.resource = self
|
17
|
-
fm.fill! if instance_accessor.prefill?
|
18
|
-
end
|
34
|
+
builder.forms.each do |form|
|
35
|
+
next unless form.action
|
36
|
+
method_name = "#{form.action}_form"
|
37
|
+
|
38
|
+
Formable.add_class_reader(clazz, method_name, form.dup)
|
39
|
+
|
40
|
+
if instance_accessor = builder.instance_accessor_for(form)
|
41
|
+
prefill_form = instance_accessor.prefill?
|
42
|
+
Formable.add_instance_reader(clazz, method_name, form.dup, prefill_form)
|
19
43
|
end
|
20
44
|
end
|
21
45
|
end
|
46
|
+
alias form_for forms_for
|
22
47
|
end
|
23
48
|
end
|
data/lib/shaf/generator.rb
CHANGED
data/lib/shaf/generator/base.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fileutils'
|
2
4
|
require 'erb'
|
3
5
|
require 'shaf/generator/helper'
|
@@ -31,13 +33,13 @@ module Shaf
|
|
31
33
|
def call; end
|
32
34
|
|
33
35
|
def template_dir
|
34
|
-
File.expand_path('
|
36
|
+
File.expand_path('templates', __dir__)
|
35
37
|
end
|
36
38
|
|
37
39
|
def read_template(file, directory = nil)
|
38
40
|
directory ||= template_dir
|
39
41
|
filename = File.join(directory, file)
|
40
|
-
filename <<
|
42
|
+
filename << '.erb' unless filename.end_with?('.erb')
|
41
43
|
File.read(filename)
|
42
44
|
end
|
43
45
|
|
@@ -46,8 +48,8 @@ module Shaf
|
|
46
48
|
locals[:changes] ||= []
|
47
49
|
b = Helper.new(locals).binding
|
48
50
|
|
49
|
-
return ERB.new(str, 0, '%-<>').result(b) if RUBY_VERSION <
|
50
|
-
ERB.new(str,trim_mode: '-<>').result(b)
|
51
|
+
return ERB.new(str, 0, '%-<>').result(b) if RUBY_VERSION < '2.6.0'
|
52
|
+
ERB.new(str, trim_mode: '-<>').result(b)
|
51
53
|
rescue SystemCallError => e
|
52
54
|
puts "Failed to render template #{template}: #{e.message}"
|
53
55
|
raise
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Generator
|
3
|
+
class Forms < Base
|
4
|
+
identifier :forms
|
5
|
+
usage 'generate forms MODEL_NAME [attribute:type[:label]] [..]'
|
6
|
+
|
7
|
+
def call
|
8
|
+
create_forms
|
9
|
+
end
|
10
|
+
|
11
|
+
def model_name
|
12
|
+
n = args.first || ''
|
13
|
+
return n unless n.empty?
|
14
|
+
raise Command::ArgumentError,
|
15
|
+
'Please provide a model name when using the forms generator!'
|
16
|
+
end
|
17
|
+
|
18
|
+
def model_class_name
|
19
|
+
Utils.model_name(model_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def class_name
|
23
|
+
"#{model_class_name}Forms"
|
24
|
+
end
|
25
|
+
|
26
|
+
def template
|
27
|
+
'api/forms.rb'
|
28
|
+
end
|
29
|
+
|
30
|
+
def target
|
31
|
+
"api/forms/#{model_name}_forms.rb"
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_forms
|
35
|
+
content = render(template, opts)
|
36
|
+
write_output(target, content)
|
37
|
+
end
|
38
|
+
|
39
|
+
def opts
|
40
|
+
{
|
41
|
+
model_name: model_name,
|
42
|
+
class_name: class_name,
|
43
|
+
model_class_name: model_class_name,
|
44
|
+
fields: fields
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def fields
|
49
|
+
args[1..-1].map do |f|
|
50
|
+
(name, type, label) = f.split(':')
|
51
|
+
label_str = label ? %(, label: "#{label}") : ''
|
52
|
+
format 'field :%s, type: "%s"%s' % [name, rewrite(type), label_str]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def rewrite(type)
|
57
|
+
case type
|
58
|
+
when /foreign_key/
|
59
|
+
'integer'
|
60
|
+
when NilClass
|
61
|
+
'string'
|
62
|
+
else
|
63
|
+
type
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/shaf/generator/model.rb
CHANGED
@@ -9,21 +9,22 @@ module Shaf
|
|
9
9
|
create_model
|
10
10
|
create_migration
|
11
11
|
create_serializer
|
12
|
+
create_forms
|
12
13
|
end
|
13
14
|
|
14
15
|
def model_name
|
15
|
-
n = args.first ||
|
16
|
+
n = args.first || ''
|
16
17
|
return n unless n.empty?
|
17
18
|
raise Command::ArgumentError,
|
18
|
-
|
19
|
+
'Please provide a model name when using the model generator!'
|
19
20
|
end
|
20
21
|
|
21
22
|
def model_class_name
|
22
|
-
Utils
|
23
|
+
Utils.model_name(model_name)
|
23
24
|
end
|
24
25
|
|
25
26
|
def table_name
|
26
|
-
Utils
|
27
|
+
Utils.pluralize model_name
|
27
28
|
end
|
28
29
|
|
29
30
|
def template
|
@@ -39,30 +40,9 @@ module Shaf
|
|
39
40
|
write_output(target, content)
|
40
41
|
end
|
41
42
|
|
42
|
-
def form_fields
|
43
|
-
args[1..-1].map do |f|
|
44
|
-
(name, type, label) = f.split(':')
|
45
|
-
label_str = label ? %(, label: "#{label}") : ''
|
46
|
-
format 'field :%s, type: "%s"%s' % [name, rewrite(type), label_str]
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def rewrite(type)
|
51
|
-
case type
|
52
|
-
when /foreign_key/
|
53
|
-
'integer'
|
54
|
-
when NilClass
|
55
|
-
'string'
|
56
|
-
else
|
57
|
-
type
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
43
|
def opts
|
62
44
|
{
|
63
|
-
model_name: model_name,
|
64
45
|
class_name: model_class_name,
|
65
|
-
form_fields: form_fields
|
66
46
|
}
|
67
47
|
end
|
68
48
|
|
@@ -76,6 +56,11 @@ module Shaf
|
|
76
56
|
serializer_args += args[1..-1].map { |arg| arg.split(':').first }
|
77
57
|
Generator::Factory.create(*serializer_args, **options).call
|
78
58
|
end
|
59
|
+
|
60
|
+
def create_forms
|
61
|
+
form_args = %W(forms #{model_name}) + args[1..-1]
|
62
|
+
Generator::Factory.create(*form_args, **options).call
|
63
|
+
end
|
79
64
|
end
|
80
65
|
end
|
81
66
|
end
|
@@ -162,21 +162,21 @@ module Shaf
|
|
162
162
|
end
|
163
163
|
|
164
164
|
def example(method, uri)
|
165
|
-
|
165
|
+
curl_args = +'-H "Authorization: abcdef \\"'
|
166
166
|
case method
|
167
167
|
when "POST"
|
168
|
-
|
168
|
+
curl_args << "\n# -d@payload \\"
|
169
169
|
when "PUT"
|
170
|
-
|
170
|
+
curl_args << "\n# -X PUT -d@payload \\"
|
171
171
|
when "DELETE"
|
172
|
-
|
172
|
+
curl_args << "\n# -X DELETE \\"
|
173
173
|
end
|
174
174
|
|
175
175
|
<<~EOS.chomp
|
176
176
|
# Example:
|
177
177
|
# ```
|
178
178
|
# curl -H "Accept: application/hal+json" \\
|
179
|
-
#
|
179
|
+
# #{curl_args}
|
180
180
|
# #{uri}
|
181
181
|
#```
|
182
182
|
EOS
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class <%= class_name %>
|
2
|
+
extend Shaf::Formable
|
3
|
+
|
4
|
+
forms_for(<%= model_class_name %>) do
|
5
|
+
<%= fields.join("\n ") %>
|
6
|
+
|
7
|
+
create do
|
8
|
+
title 'Create <%= model_class_name %>'
|
9
|
+
name 'create-<%= model_name %>'
|
10
|
+
end
|
11
|
+
|
12
|
+
edit do
|
13
|
+
instance_accessor
|
14
|
+
title 'Update <%= model_class_name %>'
|
15
|
+
name 'update-<%= model_name %>'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -1,21 +1,4 @@
|
|
1
1
|
class <%= class_name %> < Sequel::Model
|
2
|
-
extend Shaf::Formable
|
3
2
|
|
4
|
-
<% if form_fields.any? %>
|
5
|
-
form do
|
6
|
-
<%= form_fields.join("\n ") %>
|
7
|
-
|
8
|
-
create do
|
9
|
-
title 'Create <%= class_name %>'
|
10
|
-
name 'create-<%= model_name %>'
|
11
|
-
end
|
12
|
-
|
13
|
-
edit do
|
14
|
-
instance_accessor
|
15
|
-
title 'Update <%= class_name %>'
|
16
|
-
name 'update-<%= model_name %>'
|
17
|
-
end
|
18
|
-
end
|
19
|
-
<% end %>
|
20
3
|
end
|
21
4
|
|
data/lib/shaf/helpers/payload.rb
CHANGED
@@ -26,18 +26,11 @@ module Shaf
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def payload
|
29
|
-
|
30
|
-
@@payload ||= nil
|
31
|
-
|
32
|
-
if @@request_id != request.env["REQUEST_ID"]
|
33
|
-
@@request_id = request.env["REQUEST_ID"]
|
34
|
-
@@payload = parse_payload
|
35
|
-
end
|
36
|
-
@@payload
|
29
|
+
@payload ||= parse_payload
|
37
30
|
end
|
38
31
|
|
39
32
|
def read_input
|
40
|
-
request.body.rewind unless request.body.pos
|
33
|
+
request.body.rewind unless request.body.pos.zero?
|
41
34
|
request.body.read
|
42
35
|
ensure
|
43
36
|
request.body.rewind
|
@@ -45,19 +38,27 @@ module Shaf
|
|
45
38
|
|
46
39
|
def parse_payload
|
47
40
|
if request.env['CONTENT_TYPE'] == 'application/x-www-form-urlencoded'
|
48
|
-
return params.reject { |key,_| EXCLUDED_FORM_PARAMS.include? key }
|
41
|
+
return params.reject { |key, _| EXCLUDED_FORM_PARAMS.include? key }
|
49
42
|
end
|
50
43
|
|
51
44
|
input = read_input
|
52
45
|
return {} if input.empty?
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
rescue StandardError
|
60
|
-
raise Errors::BadRequestError.
|
47
|
+
raise raise_unsupported_media_type_error(request) unless suported_media_type?
|
48
|
+
|
49
|
+
JSON.parse(input, symbolize_names: true)
|
50
|
+
rescue Errors::UnsupportedMediaTypeError
|
51
|
+
raise
|
52
|
+
rescue StandardError => e
|
53
|
+
raise Errors::BadRequestError, "Failed to parse input payload: #{e.message}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def suported_media_type?
|
57
|
+
request.env['CONTENT_TYPE'].match? %r{\Aapplication/(hal\+)?json}
|
58
|
+
end
|
59
|
+
|
60
|
+
def raise_unsupported_media_type_error(request)
|
61
|
+
raise Errors::UnsupportedMediaTypeError.new(request: request)
|
61
62
|
end
|
62
63
|
|
63
64
|
def safe_params(*fields)
|
@@ -66,16 +67,14 @@ module Shaf
|
|
66
67
|
fields = fields.map { |f| f.to_sym.downcase }.to_set
|
67
68
|
fields << :id
|
68
69
|
|
69
|
-
{}
|
70
|
-
|
71
|
-
|
72
|
-
allowed[f] ||= payload[f.to_s] if payload.key? f.to_s
|
73
|
-
end
|
70
|
+
fields.each_with_object({}) do |f, allowed|
|
71
|
+
allowed[f] = payload[f] if payload.key? f
|
72
|
+
allowed[f] ||= payload[f.to_s] if payload.key? f.to_s
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
77
76
|
def ignore_form_input?(name)
|
78
|
-
|
77
|
+
name == '_method'
|
79
78
|
end
|
80
79
|
|
81
80
|
def profile(value = nil)
|
@@ -99,6 +98,7 @@ module Shaf
|
|
99
98
|
preferred_response = preferred_response_type(resource)
|
100
99
|
http_cache = kwargs.delete(:http_cache) { Settings.http_cache }
|
101
100
|
|
101
|
+
serializer ||= HALPresenter.lookup_presenter(resource)
|
102
102
|
serialized = serialize(resource, serializer, collection, **kwargs)
|
103
103
|
add_cache_headers(serialized) if http_cache
|
104
104
|
|
@@ -107,12 +107,11 @@ module Shaf
|
|
107
107
|
if preferred_response == mime_type(:html)
|
108
108
|
respond_with_html(resource, serialized)
|
109
109
|
else
|
110
|
-
respond_with_hal(resource, serialized)
|
110
|
+
respond_with_hal(resource, serialized, serializer)
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
114
|
def serialize(resource, serializer, collection, **options)
|
115
|
-
serializer ||= HALPresenter
|
116
115
|
if collection
|
117
116
|
serializer.to_collection(resource, current_user: current_user, **options)
|
118
117
|
else
|
@@ -120,9 +119,9 @@ module Shaf
|
|
120
119
|
end
|
121
120
|
end
|
122
121
|
|
123
|
-
def respond_with_hal(resource, serialized)
|
122
|
+
def respond_with_hal(resource, serialized, serializer)
|
124
123
|
log.debug "Response payload (#{resource.class}): #{serialized}"
|
125
|
-
content_type :hal, content_type_params(
|
124
|
+
content_type :hal, content_type_params(serializer)
|
126
125
|
body serialized
|
127
126
|
end
|
128
127
|
|
@@ -144,18 +143,10 @@ module Shaf
|
|
144
143
|
etag sha1, :weak # Weak or Strong??
|
145
144
|
end
|
146
145
|
|
147
|
-
def content_type_params(
|
146
|
+
def content_type_params(serializer)
|
148
147
|
return {profile: profile} if profile
|
149
148
|
|
150
|
-
|
151
|
-
case resource
|
152
|
-
when Formable::Form
|
153
|
-
Shaf::Settings.form_profile_name
|
154
|
-
when Errors::ServerError
|
155
|
-
Shaf::Settings.error_profile_name
|
156
|
-
end
|
157
|
-
|
158
|
-
{profile: name}.compact
|
149
|
+
{profile: serializer.semantic_profile}.compact
|
159
150
|
end
|
160
151
|
end
|
161
152
|
end
|
data/lib/shaf/settings.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
+
require 'shaf/utils'
|
4
5
|
|
5
6
|
module Shaf
|
6
7
|
class Settings
|
@@ -26,12 +27,8 @@ module Shaf
|
|
26
27
|
return {} unless File.exist? file
|
27
28
|
|
28
29
|
yaml = File.read(file)
|
29
|
-
if RUBY_VERSION < '2.
|
30
|
-
YAML.safe_load(yaml, [], [], true)
|
31
|
-
hash[k.to_sym] = v
|
32
|
-
end
|
33
|
-
elsif RUBY_VERSION < '2.6.0'
|
34
|
-
YAML.safe_load(yaml, [], [], true).transform_keys(&:to_sym)
|
30
|
+
if RUBY_VERSION < '2.6.0'
|
31
|
+
Utils.deep_symbolize_keys(YAML.safe_load(yaml, [], [], true))
|
35
32
|
else
|
36
33
|
YAML.safe_load(yaml, aliases: true, symbolize_names: true)
|
37
34
|
end
|
@@ -48,20 +48,24 @@ module Shaf
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def fill_form(fields)
|
52
|
-
fields.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
51
|
+
def fill_form(fields, opts = {})
|
52
|
+
fields.each_with_object({}) do |field, payload|
|
53
|
+
key = field[:name]
|
54
|
+
payload[key] = opts.fetch(key, default_field_value(field))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_field_value(field)
|
59
|
+
case field[:type]
|
60
|
+
when 'integer'
|
61
|
+
field[:name].size
|
62
|
+
when 'string'
|
63
|
+
"value for #{field[:name]}"
|
64
|
+
when 'boolean'
|
65
|
+
true
|
66
|
+
else
|
67
|
+
'type not supported'
|
68
|
+
end
|
65
69
|
end
|
66
70
|
|
67
71
|
def assert_status(code)
|
data/lib/shaf/upgrade/package.rb
CHANGED
@@ -186,7 +186,8 @@ module Shaf
|
|
186
186
|
puts '' unless @manifest.regexps.empty?
|
187
187
|
files_in(dir).all? do |file|
|
188
188
|
@manifest.regexps_for(file).all? do |name|
|
189
|
-
params =
|
189
|
+
params = YAML.safe_load(@files[name])
|
190
|
+
params.transform_keys!(&:to_sym)
|
190
191
|
apply_substitute(file, params)
|
191
192
|
end
|
192
193
|
end
|
@@ -207,11 +208,6 @@ module Shaf
|
|
207
208
|
|
208
209
|
FileUtils.mv(tmp.path, file)
|
209
210
|
end
|
210
|
-
|
211
|
-
# Refactor this when support for ruby 2.4 is dropped
|
212
|
-
def symbolize_keys(hash)
|
213
|
-
hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
|
214
|
-
end
|
215
211
|
end
|
216
212
|
end
|
217
213
|
end
|
data/lib/shaf/utils.rb
CHANGED
@@ -40,9 +40,29 @@ module Shaf
|
|
40
40
|
def symbol_string(str)
|
41
41
|
symbolize(str).inspect
|
42
42
|
end
|
43
|
+
|
44
|
+
def rackify_header(str)
|
45
|
+
return if str.nil?
|
46
|
+
str.upcase.tr('-', '_').tap do |key|
|
47
|
+
key.prepend('HTTP_') unless key.start_with? 'HTTP_'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def deep_symbolize_keys(value)
|
52
|
+
case value
|
53
|
+
when Hash
|
54
|
+
value.each_with_object({}) do |(k, v), h|
|
55
|
+
h[k.to_sym] = deep_symbolize_keys(v)
|
56
|
+
end
|
57
|
+
when Array
|
58
|
+
value.map { |v| deep_symbolize_keys(v) }
|
59
|
+
else
|
60
|
+
value
|
61
|
+
end
|
62
|
+
end
|
43
63
|
end
|
44
64
|
|
45
|
-
def_delegators Utils, :pluralize, :singularize, :symbolize, :symbol_string, :gem_root
|
65
|
+
def_delegators Utils, :pluralize, :singularize, :symbolize, :symbol_string, :gem_root, :rackify_header
|
46
66
|
|
47
67
|
def project_root
|
48
68
|
return @project_root if defined? @project_root
|
@@ -73,9 +93,9 @@ module Shaf
|
|
73
93
|
end
|
74
94
|
end
|
75
95
|
|
76
|
-
def bootstrap
|
96
|
+
def bootstrap(env: 'development')
|
77
97
|
in_project_root do
|
78
|
-
ENV['RACK_ENV'] ||=
|
98
|
+
ENV['RACK_ENV'] ||= env
|
79
99
|
require 'config/bootstrap'
|
80
100
|
yield if block_given?
|
81
101
|
end
|
data/lib/shaf/version.rb
CHANGED
@@ -2,8 +2,8 @@ require 'serializers/base_serializer'
|
|
2
2
|
require 'shaf/formable'
|
3
3
|
|
4
4
|
class FormSerializer < BaseSerializer
|
5
|
-
|
6
5
|
model Shaf::Formable::Form
|
6
|
+
profile Shaf::Settings.form_profile_name
|
7
7
|
|
8
8
|
attribute :method do
|
9
9
|
(options[:method] || resource&.method || 'POST').to_s.upcase
|
@@ -25,7 +25,7 @@ class FormSerializer < BaseSerializer
|
|
25
25
|
|
26
26
|
post_serialize do |hash|
|
27
27
|
fields = resource&.fields
|
28
|
-
|
28
|
+
next if fields.nil? || fields.empty?
|
29
29
|
hash[:fields] = fields.map do |field|
|
30
30
|
{
|
31
31
|
name: field.name,
|
data/templates/config/paths.rb
CHANGED
@@ -5,3 +5,4 @@ Shaf::Settings.app_root = app_root
|
|
5
5
|
Shaf::Settings.app_dir = File.expand_path('api', app_root)
|
6
6
|
Shaf::Settings.src_dir = File.expand_path('src', app_root)
|
7
7
|
Shaf::Settings.lib_dir = File.expand_path('lib', app_root)
|
8
|
+
Shaf::Settings.spec_dir = File.expand_path('spec', app_root)
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shaf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sammy Henningsson
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
ZMhjYR7sRczGJx+GxGU2EaR0bjRsPVlC4ywtFxoOfRG3WaJcpWGEoAoMJX6Z0bRv
|
31
31
|
M40=
|
32
32
|
-----END CERTIFICATE-----
|
33
|
-
date: 2019-
|
33
|
+
date: 2019-08-31 00:00:00.000000000 Z
|
34
34
|
dependencies:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: minitest
|
@@ -141,6 +141,10 @@ files:
|
|
141
141
|
- lib/shaf/command/new.rb
|
142
142
|
- lib/shaf/command/server.rb
|
143
143
|
- lib/shaf/command/templates/Gemfile.erb
|
144
|
+
- lib/shaf/command/test.rb
|
145
|
+
- lib/shaf/command/test/filter.rb
|
146
|
+
- lib/shaf/command/test/runnable_method.rb
|
147
|
+
- lib/shaf/command/test/runner.rb
|
144
148
|
- lib/shaf/command/upgrade.rb
|
145
149
|
- lib/shaf/command/version.rb
|
146
150
|
- lib/shaf/doc_model.rb
|
@@ -158,6 +162,7 @@ files:
|
|
158
162
|
- lib/shaf/generator.rb
|
159
163
|
- lib/shaf/generator/base.rb
|
160
164
|
- lib/shaf/generator/controller.rb
|
165
|
+
- lib/shaf/generator/forms.rb
|
161
166
|
- lib/shaf/generator/helper.rb
|
162
167
|
- lib/shaf/generator/migration.rb
|
163
168
|
- lib/shaf/generator/migration/add_column.rb
|
@@ -174,6 +179,7 @@ files:
|
|
174
179
|
- lib/shaf/generator/scaffold.rb
|
175
180
|
- lib/shaf/generator/serializer.rb
|
176
181
|
- lib/shaf/generator/templates/api/controller.rb.erb
|
182
|
+
- lib/shaf/generator/templates/api/forms.rb.erb
|
177
183
|
- lib/shaf/generator/templates/api/model.rb.erb
|
178
184
|
- lib/shaf/generator/templates/api/policy.rb.erb
|
179
185
|
- lib/shaf/generator/templates/api/serializer.rb.erb
|
@@ -253,6 +259,7 @@ files:
|
|
253
259
|
- upgrades/0.6.0.tar.gz
|
254
260
|
- upgrades/1.0.0.tar.gz
|
255
261
|
- upgrades/1.0.4.tar.gz
|
262
|
+
- upgrades/1.1.0.tar.gz
|
256
263
|
homepage:
|
257
264
|
licenses:
|
258
265
|
- MIT
|
@@ -267,7 +274,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
267
274
|
requirements:
|
268
275
|
- - ">="
|
269
276
|
- !ruby/object:Gem::Version
|
270
|
-
version: '2.
|
277
|
+
version: '2.5'
|
271
278
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
272
279
|
requirements:
|
273
280
|
- - ">="
|
metadata.gz.sig
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
E����{�1ߧl��(�L�Ct���^�x�>7�V�I
|
1
|
+
|�_�σD�c_O�dC9� N��S�C6��X=����3szW>�(v���;�}fag<
|