shaf 0.1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -0
  4. data/bin/shaf +57 -0
  5. data/lib/shaf.rb +9 -0
  6. data/lib/shaf/api_doc.rb +124 -0
  7. data/lib/shaf/api_doc/comment.rb +27 -0
  8. data/lib/shaf/api_doc/document.rb +133 -0
  9. data/lib/shaf/app.rb +22 -0
  10. data/lib/shaf/command.rb +42 -0
  11. data/lib/shaf/command/console.rb +17 -0
  12. data/lib/shaf/command/generate.rb +19 -0
  13. data/lib/shaf/command/new.rb +79 -0
  14. data/lib/shaf/command/server.rb +15 -0
  15. data/lib/shaf/command/templates/Gemfile.erb +30 -0
  16. data/lib/shaf/doc_model.rb +54 -0
  17. data/lib/shaf/errors.rb +77 -0
  18. data/lib/shaf/extensions.rb +11 -0
  19. data/lib/shaf/extensions/authorize.rb +42 -0
  20. data/lib/shaf/extensions/resource_uris.rb +153 -0
  21. data/lib/shaf/formable.rb +188 -0
  22. data/lib/shaf/generator.rb +69 -0
  23. data/lib/shaf/generator/controller.rb +106 -0
  24. data/lib/shaf/generator/migration.rb +122 -0
  25. data/lib/shaf/generator/migration/add_column.rb +49 -0
  26. data/lib/shaf/generator/migration/create_table.rb +40 -0
  27. data/lib/shaf/generator/migration/drop_column.rb +45 -0
  28. data/lib/shaf/generator/migration/empty.rb +21 -0
  29. data/lib/shaf/generator/migration/rename_column.rb +48 -0
  30. data/lib/shaf/generator/model.rb +68 -0
  31. data/lib/shaf/generator/policy.rb +43 -0
  32. data/lib/shaf/generator/scaffold.rb +26 -0
  33. data/lib/shaf/generator/serializer.rb +258 -0
  34. data/lib/shaf/generator/templates/api/controller.rb.erb +62 -0
  35. data/lib/shaf/generator/templates/api/model.rb.erb +20 -0
  36. data/lib/shaf/generator/templates/api/policy.rb.erb +26 -0
  37. data/lib/shaf/generator/templates/api/serializer.rb.erb +24 -0
  38. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +98 -0
  39. data/lib/shaf/generator/templates/spec/model.rb.erb +40 -0
  40. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +46 -0
  41. data/lib/shaf/helpers.rb +15 -0
  42. data/lib/shaf/helpers/json_html.rb +65 -0
  43. data/lib/shaf/helpers/paginate.rb +24 -0
  44. data/lib/shaf/helpers/payload.rb +115 -0
  45. data/lib/shaf/helpers/session.rb +53 -0
  46. data/lib/shaf/middleware.rb +1 -0
  47. data/lib/shaf/middleware/request_id.rb +16 -0
  48. data/lib/shaf/registrable_factory.rb +71 -0
  49. data/lib/shaf/settings.rb +33 -0
  50. data/lib/shaf/spec.rb +6 -0
  51. data/lib/shaf/spec/http_method_utils.rb +24 -0
  52. data/lib/shaf/spec/integration_spec.rb +53 -0
  53. data/lib/shaf/spec/model.rb +17 -0
  54. data/lib/shaf/spec/payload_test.rb +78 -0
  55. data/lib/shaf/spec/payload_utils.rb +176 -0
  56. data/lib/shaf/spec/serializer_spec.rb +24 -0
  57. data/lib/shaf/tasks.rb +4 -0
  58. data/lib/shaf/tasks/db.rb +61 -0
  59. data/lib/shaf/tasks/test.rb +43 -0
  60. data/lib/shaf/utils.rb +53 -0
  61. data/lib/shaf/version.rb +3 -0
  62. data/templates/Rakefile +13 -0
  63. data/templates/api/controllers/base_controller.rb +57 -0
  64. data/templates/api/controllers/docs_controller.rb +16 -0
  65. data/templates/api/controllers/root_controller.rb +8 -0
  66. data/templates/api/serializers/error_serializer.rb +10 -0
  67. data/templates/api/serializers/form_serializer.rb +42 -0
  68. data/templates/api/serializers/root_serializer.rb +16 -0
  69. data/templates/config.ru +4 -0
  70. data/templates/config/bootstrap.rb +12 -0
  71. data/templates/config/constants.rb +5 -0
  72. data/templates/config/customize.rb +3 -0
  73. data/templates/config/database.rb +40 -0
  74. data/templates/config/directories.rb +32 -0
  75. data/templates/config/helpers.rb +18 -0
  76. data/templates/config/initializers.rb +12 -0
  77. data/templates/config/initializers/db_migrations.rb +18 -0
  78. data/templates/config/initializers/hal_presenter.rb +6 -0
  79. data/templates/config/initializers/logging.rb +7 -0
  80. data/templates/config/initializers/sequel.rb +4 -0
  81. data/templates/config/settings.yml +19 -0
  82. data/templates/frontend/assets/css/main.css +70 -0
  83. data/templates/frontend/views/form.erb +16 -0
  84. data/templates/frontend/views/layout.erb +11 -0
  85. data/templates/frontend/views/payload.erb +8 -0
  86. data/templates/spec/integration/root_spec.rb +14 -0
  87. data/templates/spec/serializers/root_serializer_spec.rb +12 -0
  88. data/templates/spec/spec_helper.rb +4 -0
  89. metadata +348 -0
  90. metadata.gz.sig +0 -0
@@ -0,0 +1,188 @@
1
+ module Shaf
2
+ module Formable
3
+ class Field
4
+ attr_reader :name, :type, :value, :label
5
+
6
+ def initialize(name, params = {})
7
+ @name = name
8
+ @type = params[:type]
9
+ @label = params[:label]
10
+ @has_value = params.key? :value
11
+ @value = params[:value]
12
+ end
13
+
14
+ def has_value?
15
+ @has_value
16
+ end
17
+
18
+ def to_html
19
+ [
20
+ '<div class="form--input-group">',
21
+ label_element,
22
+ input_element,
23
+ '</div>'
24
+ ].compact.join("\n")
25
+ end
26
+
27
+ private
28
+
29
+ def label_element
30
+ str = (label || name || "").to_s
31
+ %Q(<label for="#{name}" class="form--label">#{str}</label>)
32
+ end
33
+
34
+ def input_element
35
+ _value = value ? %Q( value="#{value.to_s}") : ""
36
+ %Q(<input type="#{type.to_s}" class="form--input" id="#{name.to_s}" name="#{name.to_s}"#{_value}>)
37
+ end
38
+ end
39
+
40
+ class Form
41
+
42
+ DEFAULT_TYPE = 'application/json'
43
+
44
+ attr_accessor :resource, :name, :title, :href, :type, :self_link
45
+ attr_reader :fields
46
+
47
+ def initialize(params = {})
48
+ @name = params[:name]
49
+ @title = params[:title]
50
+ @method = params[:method] || 'POST'
51
+ @type = params[:type] || DEFAULT_TYPE
52
+ @fields = (params[:fields] || {}).map { |name, args| Field.new(name, args) }
53
+ end
54
+
55
+ def method=(m)
56
+ @method = m.to_s.upcase
57
+ end
58
+
59
+ def method
60
+ @method.to_s.upcase
61
+ end
62
+
63
+ def fields=(fields)
64
+ @fields = fields.map { |name, args| Field.new(name, args) }
65
+ end
66
+
67
+ def add_field(name, opts)
68
+ @fields << Field.new(name, opts)
69
+ end
70
+
71
+ def to_html
72
+ form_element do
73
+ [
74
+ hidden_method_element,
75
+ fields.map { |f| f.to_html }.join("\n"),
76
+ submit_element,
77
+ ].compact.join("\n")
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def form_element
84
+ [
85
+ %Q(<form class="form" method=#{method == 'GET' ? 'GET' : 'POST'}#{href ? %Q( action="#{href.to_s}") : ''}>),
86
+ block_given? ? yield : nil,
87
+ "</form>",
88
+ ].compact.join("\n")
89
+ end
90
+
91
+ def hidden_method_element
92
+ return if ['GET', 'POST'].include?(method)
93
+ %Q(<input type="hidden" name="_method" value="#{method}">)
94
+ end
95
+
96
+ def submit_element
97
+ %Q(<div class="form--input-group"><input type="submit" class="button" value="Submit"</div>)
98
+ end
99
+ end
100
+
101
+ class Builder
102
+ def self.call(block)
103
+ [
104
+ new(block, create: true).call,
105
+ new(block, edit: true).call
106
+ ]
107
+ end
108
+
109
+ attr_reader :block
110
+
111
+ def initialize(block, create: false, edit: false)
112
+ @block = block
113
+ @create = create
114
+ @edit = edit
115
+ @form = nil
116
+ @default_method = @create ? :post : :put
117
+ end
118
+
119
+ def call
120
+ instance_exec(&block)
121
+ @form
122
+ end
123
+
124
+ def form
125
+ @form ||= Form.new(method: @default_method)
126
+ end
127
+
128
+ def name(name)
129
+ form.name = name
130
+ end
131
+
132
+ def title(title)
133
+ form.title = title
134
+ end
135
+
136
+ def method(method)
137
+ form.method = method
138
+ end
139
+
140
+ def type(type)
141
+ form.type = type
142
+ end
143
+
144
+ def fields(fields)
145
+ form.fields = fields
146
+ end
147
+
148
+ def field(name, opts = {})
149
+ form.add_field(name, opts)
150
+ end
151
+
152
+ def create(&b)
153
+ return unless @create
154
+ call_nested_block(b)
155
+ end
156
+
157
+ def edit(&b)
158
+ return unless @edit
159
+ call_nested_block(b)
160
+ end
161
+
162
+ def call_nested_block(b)
163
+ old, @block = @block, b
164
+ call
165
+ ensure
166
+ @block = old
167
+ end
168
+ end
169
+
170
+ module ClassMethods
171
+ attr_reader :create_form, :edit_form
172
+
173
+ def form(&block)
174
+ @create_form, @edit_form = Builder.(block)
175
+ end
176
+ end
177
+
178
+ def self.included(base)
179
+ base.extend(ClassMethods)
180
+ end
181
+
182
+ def edit_form
183
+ self.class.edit_form.tap do |form|
184
+ form&.resource = self
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,69 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+ require 'ostruct'
4
+ require 'shaf/registrable_factory'
5
+
6
+ module Shaf
7
+ module Generator
8
+ class Factory
9
+ extend RegistrableFactory
10
+ end
11
+
12
+ class Base
13
+ attr_reader :args
14
+
15
+ class << self
16
+ def inherited(child)
17
+ Factory.register(child)
18
+ end
19
+
20
+ def identifier(*ids)
21
+ @identifiers = ids.flatten
22
+ end
23
+
24
+ def usage(str = nil, &block)
25
+ @usage = str || block
26
+ end
27
+ end
28
+
29
+ def initialize(*args)
30
+ @args = args.dup
31
+ end
32
+
33
+ def template_dir
34
+ File.expand_path('../generator/templates', __FILE__)
35
+ end
36
+
37
+ def read_template(file, directory = nil)
38
+ directory ||= template_dir
39
+ filename = File.join(directory, file)
40
+ filename << ".erb" unless filename.end_with?(".erb")
41
+ File.read(filename)
42
+ end
43
+
44
+ def render(template, locals = {})
45
+ str = read_template(template)
46
+ locals[:changes] ||= []
47
+ b = OpenStruct.new(locals).instance_eval { binding }
48
+ ERB.new(str, 0, '%-<>').result(b)
49
+ rescue SystemCallError => e
50
+ puts "Failed to render template #{template}: #{e.message}"
51
+ raise
52
+ end
53
+
54
+ def write_output(file, content)
55
+ dir = File.dirname(file)
56
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
57
+ File.write(file, content)
58
+ puts "Added: #{file}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ require 'shaf/generator/controller'
65
+ require 'shaf/generator/migration'
66
+ require 'shaf/generator/model'
67
+ require 'shaf/generator/policy'
68
+ require 'shaf/generator/scaffold'
69
+ require 'shaf/generator/serializer'
@@ -0,0 +1,106 @@
1
+ module Shaf
2
+ module Generator
3
+ class Controller < Base
4
+
5
+ identifier :controller
6
+ usage 'generate controller RESOURCE_NAME [attribute:type] [..]'
7
+
8
+ def call
9
+ if name.empty?
10
+ raise Command::ArgumentError,
11
+ "Please provide a controller name when using the controller generator!"
12
+ end
13
+
14
+ create_controller
15
+ create_integration_spec
16
+ add_link_to_root
17
+ end
18
+
19
+ def name
20
+ args.first || ""
21
+ end
22
+
23
+ def params
24
+ args[1..-1].map { |param| param.split(':')}
25
+ end
26
+
27
+ def plural_name
28
+ Utils::pluralize(name)
29
+ end
30
+
31
+ def template
32
+ 'api/controller.rb'
33
+ end
34
+
35
+ def spec_template
36
+ 'spec/integration_spec.rb'
37
+ end
38
+
39
+ def target
40
+ "api/controllers/#{plural_name}_controller.rb"
41
+ end
42
+
43
+ def spec_target
44
+ "spec/integration/#{plural_name}_controller_spec.rb"
45
+ end
46
+
47
+ def create_controller
48
+ content = render(template, opts)
49
+ write_output(target, content)
50
+ end
51
+
52
+ def create_integration_spec
53
+ content = render(spec_template, opts)
54
+ write_output(spec_target, content)
55
+ end
56
+
57
+ def opts
58
+ {
59
+ name: name,
60
+ plural_name: plural_name,
61
+ serializer_class_name: "#{name.capitalize}Serializer",
62
+ model_class_name: name.capitalize,
63
+ controller_class_name: "#{plural_name.capitalize}Controller",
64
+ policy_class_name: "#{name.capitalize}Policy",
65
+ policy_file: "policies/#{name}_policy",
66
+ params: params
67
+ }
68
+ end
69
+
70
+ def add_link_to_root
71
+ file = "api/serializers/root_serializer.rb"
72
+ unless File.exist? file
73
+ puts "Warning: file '#{file}' does not exist. "\
74
+ "Not adding any link to the #{plural_name} collection"
75
+ end
76
+ added = false
77
+ content = []
78
+ File.readlines(file).reverse.each do |line|
79
+ if match = !added && line.match(/^(\s*)link /)
80
+ content.unshift link_content("#{match[1]}")
81
+ added = true
82
+ end
83
+ content.unshift(line)
84
+ end
85
+ File.open(file, 'w') { |f| f.puts content }
86
+ puts "Modified: #{file}"
87
+ end
88
+
89
+ def link_content(indentation = "")
90
+ <<~EOS.split("\n").map { |line| "#{indentation}#{line}" }
91
+
92
+ # Auto generated doc:
93
+ # Link to the collection of #{plural_name}.
94
+ # Method: GET
95
+ # Example:
96
+ # ```
97
+ # curl -H "Accept: application/json" \\
98
+ # -H "Authorization: abcdef" \\
99
+ # /#{plural_name}/5
100
+ #```
101
+ link :#{plural_name}, #{plural_name}_uri
102
+ EOS
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,122 @@
1
+ require 'date'
2
+
3
+ module Shaf
4
+ module Generator
5
+ module Migration
6
+
7
+ class Factory
8
+ extend RegistrableFactory
9
+ end
10
+
11
+ class Generator < Generator::Base
12
+ identifier :migration
13
+ usage { Factory.usage }
14
+
15
+ def call
16
+ generator = args.empty? ? Empty.new : Factory.create(*args)
17
+ (target, content) = generator.call
18
+ write_output(target, content)
19
+ rescue StandardError => e
20
+ raise Command::ArgumentError, e.message
21
+ end
22
+ end
23
+
24
+ class Base
25
+ DB_COL_TYPES = {
26
+ integer: ['Integer :%s', ':%s, Integer'],
27
+ varchar: ['String %s', ':%s, String'],
28
+ string: ['String :%s', ':%s, String'],
29
+ text: ['String :%s, text: true', ':%s, String, text: true'],
30
+ blob: ['File :%s', ':%s, File'],
31
+ bigint: ['Bignum :%s', ':%s, Bignum'],
32
+ double: ['Float :%s', ':%s, Float'],
33
+ numeric: ['BigDecimal :%s', ':%s, BigDecimal'],
34
+ date: ['Date :%s', ':%s, Date'],
35
+ timestamp: ['DateTime :%s', ':%s, DateTime'],
36
+ time: ['Time :%s', ':%s, Time'],
37
+ bool: ['TrueClass :%s', ':%s, TrueClass'],
38
+ boolean: ['TrueClass :%s', ':%s, TrueClass'],
39
+ }
40
+
41
+ attr_reader :args
42
+
43
+ class << self
44
+ def inherited(child)
45
+ Factory.register(child)
46
+ end
47
+
48
+ def identifier(*ids)
49
+ @identifiers = ids.flatten.map(&:to_s)
50
+ end
51
+
52
+ def usage(str = nil, &block)
53
+ @usage = str || block
54
+ end
55
+ end
56
+
57
+ def initialize(*args)
58
+ @args = args.dup
59
+ end
60
+
61
+ def call
62
+ validate_args
63
+ name = compile_migration_name
64
+ compile_changes
65
+ [target(name), render]
66
+ rescue StandardError => e
67
+ raise Command::ArgumentError, e.message
68
+ end
69
+
70
+ def add_change(change)
71
+ @changes ||= []
72
+ @changes << change if change
73
+ end
74
+
75
+ def db_type(type)
76
+ type ||= :string
77
+ DB_COL_TYPES[type.to_sym] or raise "Column type '#{type}' not supported"
78
+ end
79
+
80
+ def column_def(str, create: true)
81
+ name, type = str.split(':')
82
+ format db_type(type)[create ? 0 : 1], name.downcase
83
+ end
84
+
85
+ def target(name)
86
+ raise "Migration filename is nil" unless name
87
+ "db/migrations/#{timestamp}_#{name}.rb"
88
+ end
89
+
90
+ private
91
+
92
+ def timestamp
93
+ DateTime.now.strftime("%Y%m%d%H%M%S")
94
+ end
95
+
96
+ def add_timestamp_columns?
97
+ if File.exist? 'config/initializers/sequel.rb'
98
+ require 'config/initializers/sequel'
99
+ Sequel::Model.plugins.include? Sequel::Plugins::Timestamps
100
+ end
101
+ end
102
+
103
+ def render
104
+ <<~EOS
105
+ Sequel.migration do
106
+ change do
107
+ #{@changes.flatten.join("\n ")}
108
+ end
109
+ end
110
+ EOS
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+
118
+ require 'shaf/generator/migration/add_column'
119
+ require 'shaf/generator/migration/create_table'
120
+ require 'shaf/generator/migration/drop_column'
121
+ require 'shaf/generator/migration/empty'
122
+ require 'shaf/generator/migration/rename_column'