shaf 0.1.0.beta

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 (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,49 @@
1
+ module Shaf
2
+ module Generator
3
+ module Migration
4
+ class AddColumn < Base
5
+
6
+ identifier %w(add column)
7
+ usage 'generate migration add column TABLE_NAME field:type'
8
+
9
+ def validate_args
10
+ if (table_name || "").empty?
11
+ raise "Please provide a table and at least " \
12
+ "one column when generation add column migration"
13
+ elsif args.size < 2 || (args[1] || "").empty?
14
+ raise "Please provide at least one column when " \
15
+ "generation add column migration"
16
+ end
17
+ end
18
+
19
+ def compile_migration_name
20
+ cols = columns.map { |c| c.split(':').first }
21
+ "add_#{cols.join('_')}_to_#{table_name}"
22
+ end
23
+
24
+ def table_name
25
+ args.first
26
+ end
27
+
28
+ def compile_changes
29
+ add_change add_columns_change
30
+ end
31
+
32
+ def columns
33
+ args[1..-1]
34
+ end
35
+
36
+ def add_columns_change
37
+ cols = columns.map do |s|
38
+ "add_column #{column_def(s, create: false)}"
39
+ end
40
+ [
41
+ "alter_table(:#{table_name}) do",
42
+ *cols.map { |col| col.prepend(" ") }, # indent body with 2 spaces
43
+ "end\n"
44
+ ]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ module Shaf
2
+ module Generator
3
+ module Migration
4
+ class CreateTable < Base
5
+
6
+ identifier %w(create table)
7
+ usage 'generate migration create table TABLE_NAME [field:type] [..]'
8
+
9
+ def validate_args
10
+ return unless table_name.empty?
11
+ raise "Please provide a table name when generation a create table migration"
12
+ end
13
+
14
+ def compile_migration_name
15
+ "create_#{table_name}_table"
16
+ end
17
+
18
+ def table_name
19
+ args.first || ""
20
+ end
21
+
22
+ def compile_changes
23
+ add_change create_table_change
24
+ end
25
+
26
+ def create_table_change
27
+ cols = ["primary_key :id"]
28
+ cols += ["DateTime :created_at", "DateTime :updated_at"] if add_timestamp_columns?
29
+ cols += args[1..-1].map { |s| column_def(s) }
30
+ [
31
+ "create_table(:#{table_name}) do",
32
+ *cols.map { |col| col.prepend(" ") }, # indent body with 2 spaces
33
+ "end\n\n"
34
+ ]
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ module Shaf
2
+ module Generator
3
+ module Migration
4
+ class DropColumn < Base
5
+
6
+ identifier %w(drop column)
7
+ usage 'generate migration drop column TABLE_NAME COLUMN_NAME'
8
+
9
+ def validate_args
10
+ if table_name.empty?
11
+ raise "Please provide a table and at least " \
12
+ "one column when generation add column migration"
13
+ elsif column.empty?
14
+ raise "Please provide at least one column when " \
15
+ "generation add column migration"
16
+ end
17
+ end
18
+
19
+ def compile_migration_name
20
+ "drop_#{column}_from_#{table_name}"
21
+ end
22
+
23
+ def compile_changes
24
+ add_change drop_column_change
25
+ end
26
+
27
+ def table_name
28
+ args.first || ""
29
+ end
30
+
31
+ def column
32
+ args[1] || ""
33
+ end
34
+
35
+ def drop_column_change
36
+ [
37
+ "alter_table(:#{table_name}) do",
38
+ " drop_column :#{column}",
39
+ "end\n"
40
+ ]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ module Shaf
2
+ module Generator
3
+ module Migration
4
+ class Empty < Base
5
+
6
+ identifier %r(\A\Z)
7
+ usage 'generate migration'
8
+
9
+ def validate_args; end
10
+
11
+ def compile_migration_name
12
+ "empty"
13
+ end
14
+
15
+ def compile_changes
16
+ add_change nil
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ module Shaf
2
+ module Generator
3
+ module Migration
4
+ class RenameColumn < Base
5
+
6
+ identifier %w(rename column)
7
+ usage 'generate migration rename column TABLE_NAME OLD_NAME NEW_NAME'
8
+
9
+ def validate_args
10
+ if table_name.empty?
11
+ raise "Please provide a table and at least " \
12
+ "one column when generation add column migration"
13
+ elsif from_col.empty? || to_col.empty?
14
+ raise "Please provide the old column name and the new column name"
15
+ end
16
+ end
17
+
18
+ def compile_migration_name
19
+ "rename_#{table_name}_#{from_col}_to_#{to_col}"
20
+ end
21
+
22
+ def compile_changes
23
+ add_change rename_column_change
24
+ end
25
+
26
+ def table_name
27
+ args.first || ""
28
+ end
29
+
30
+ def from_col
31
+ args[1] || ""
32
+ end
33
+
34
+ def to_col
35
+ args[2] || ""
36
+ end
37
+
38
+ def rename_column_change
39
+ [
40
+ "alter_table(:#{table_name}) do",
41
+ " rename_column :#{from_col}, :#{to_col}",
42
+ "end\n"
43
+ ]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,68 @@
1
+ module Shaf
2
+ module Generator
3
+ class Model < Base
4
+
5
+ identifier :model
6
+ usage 'generate model MODEL_NAME [attribute:type] [..]'
7
+
8
+ def call
9
+ if model_name.empty?
10
+ raise "Please provide a model name when using the model generator!"
11
+ end
12
+
13
+ create_model
14
+ create_migration
15
+ create_serializer
16
+ end
17
+
18
+ def model_name
19
+ args.first || ""
20
+ end
21
+
22
+ def table_name
23
+ Utils::pluralize model_name
24
+ end
25
+
26
+ def template
27
+ 'api/model.rb'
28
+ end
29
+
30
+ def target
31
+ "api/models/#{model_name}.rb"
32
+ end
33
+
34
+ def create_model
35
+ content = render(template, opts)
36
+ write_output(target, content)
37
+ end
38
+
39
+ def form_fields
40
+ args[1..-1].map do |f|
41
+ (name, type, label) = f.split(':')
42
+ type ||= "string"
43
+ label_str = label ? %Q(, label: "#{label}") : ""
44
+ format 'field :%s, type: "%s"%s' % [name, type, label_str]
45
+ end
46
+ end
47
+
48
+ def opts
49
+ {
50
+ model_name: model_name,
51
+ class_name: model_name.capitalize,
52
+ form_fields: form_fields
53
+ }
54
+ end
55
+
56
+ def create_migration
57
+ migration_args = %W(create table #{table_name}) + args[1..-1]
58
+ Migration::Generator.new(*migration_args).call
59
+ end
60
+
61
+ def create_serializer
62
+ serializer_args = %W(serializer #{model_name})
63
+ serializer_args += args[1..-1].map { |arg| arg.split(':').first }
64
+ Generator::Factory.create(*serializer_args).call
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ module Shaf
2
+ module Generator
3
+ class Policy < Base
4
+ identifier :policy
5
+ usage 'generate policy MODEL_NAME [attribute] [..]'
6
+
7
+ def call
8
+ if policy_name.empty?
9
+ raise "Please provide a policy name when using the policy generator!"
10
+ end
11
+ create_policy
12
+ end
13
+
14
+ def policy_name
15
+ args.first || ""
16
+ end
17
+
18
+ def template
19
+ 'api/policy.rb'
20
+ end
21
+
22
+ def target
23
+ "api/policies/#{policy_name}_policy.rb"
24
+ end
25
+
26
+ def create_policy
27
+ content = render(template, opts)
28
+ write_output(target, content)
29
+ end
30
+
31
+ def attributes
32
+ args[1..-1].map { |attr| "attribute :#{attr}" }
33
+ end
34
+
35
+ def opts
36
+ {
37
+ policy_class_name: "#{policy_name.capitalize}Policy",
38
+ attributes: attributes,
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ module Shaf
2
+ module Generator
3
+ class Scaffold < Base
4
+
5
+ identifier :scaffold
6
+ usage 'generate scaffold RESOURCE_NAME [attribute:type] [..]'
7
+
8
+ def call
9
+ if name.empty?
10
+ raise "Please provide a resource name when using the scaffold generator!"
11
+ end
12
+
13
+ Generator::Factory.create('model', *args).call
14
+ Generator::Factory.create('controller', *controller_args).call
15
+ end
16
+
17
+ def name
18
+ args.first || ""
19
+ end
20
+
21
+ def controller_args
22
+ [name] + args[1..-1]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,258 @@
1
+ module Shaf
2
+ module Generator
3
+ class Serializer < Base
4
+ identifier :serializer
5
+ usage 'generate serializer MODEL_NAME [attribute] [..]'
6
+
7
+ def call
8
+ if name.empty?
9
+ raise "Please provide a model name when using the serializer generator!"
10
+ end
11
+
12
+ create_serializer
13
+ create_serializer_spec
14
+ create_policy
15
+ end
16
+
17
+ def name
18
+ args.first || ""
19
+ end
20
+
21
+ def plural_name
22
+ Utils.pluralize(name)
23
+ end
24
+
25
+ def model_class_name
26
+ "::#{name.capitalize}"
27
+ end
28
+
29
+ def policy_class_name
30
+ "::#{name.capitalize}Policy"
31
+ end
32
+
33
+ def template
34
+ 'api/serializer.rb'
35
+ end
36
+
37
+ def spec_template
38
+ 'spec/serializer_spec.rb'
39
+ end
40
+
41
+ def target
42
+ "api/serializers/#{name}_serializer.rb"
43
+ end
44
+
45
+ def spec_target
46
+ "spec/serializers/#{name}_serializer_spec.rb"
47
+ end
48
+
49
+ def create_serializer
50
+ content = render(template, opts)
51
+ write_output(target, content)
52
+ end
53
+
54
+ def create_serializer_spec
55
+ content = render(spec_template, opts)
56
+ write_output(spec_target, content)
57
+ end
58
+
59
+ def attributes
60
+ args[1..-1].map { |attr| ":#{attr}" }
61
+ end
62
+
63
+ def attributes_with_doc
64
+ attributes.map do |attr|
65
+ [
66
+ "# FIXME: Write documentation for attribute #{attr}",
67
+ "attribute #{attr}"
68
+ ]
69
+ end
70
+ end
71
+
72
+ def links
73
+ %w(doc:up self doc:create-form doc:edit-form doc:edit doc:delete)
74
+ end
75
+
76
+ def curies_with_doc
77
+ [
78
+ <<~EOS.split("\n")
79
+ # Auto generated doc:
80
+ # Link to the documentation for a given relation of the #{name} resource.
81
+ # This link is templated, which means that {rel} must be replaced by the
82
+ # appropriate relation name.
83
+ # Method: GET
84
+ # Example:
85
+ # ```
86
+ # curl -H "Accept: application/json" \\
87
+ # /doc/#{name}/rels/edit
88
+ #```
89
+ curie :doc do
90
+ doc_curie_uri('#{name}')
91
+ end
92
+ EOS
93
+ ]
94
+ end
95
+
96
+ def links_with_doc
97
+ [
98
+ collection_link,
99
+ self_link,
100
+ new_link,
101
+ edit_link,
102
+ update_link,
103
+ delete_link,
104
+ ]
105
+ end
106
+
107
+ def collection_link
108
+ link(
109
+ rel: "doc:up",
110
+ desc: "Link to the collection of all #{plural_name}. " \
111
+ "Send a POST request to this uri to create a new #{name}",
112
+ method: "GET or POST",
113
+ uri: "/#{plural_name}",
114
+ uri_helper: "#{plural_name}_uri"
115
+ )
116
+ end
117
+
118
+ def self_link
119
+ link(
120
+ rel: "self",
121
+ desc: "Link to this #{name}",
122
+ uri: "/#{plural_name}/5",
123
+ uri_helper: "#{name}_uri(resource)"
124
+ )
125
+ end
126
+
127
+ def new_link
128
+ link(
129
+ rel: "doc:create-form",
130
+ desc: "Link to a form to create a new #{name}",
131
+ uri: "/#{plural_name}/form",
132
+ uri_helper: "new_#{name}_uri"
133
+ )
134
+ end
135
+
136
+ def edit_link
137
+ link(
138
+ rel: "doc:edit-form",
139
+ desc: "Link to a form to edit this resource",
140
+ uri: "/#{plural_name}/5/edit",
141
+ uri_helper: "edit_#{name}_uri(resource)"
142
+ )
143
+ end
144
+
145
+ def update_link
146
+ link(
147
+ rel: "doc:edit",
148
+ desc: "Link to update this #{name}",
149
+ method: "PUT",
150
+ uri: "/#{plural_name}/5",
151
+ uri_helper: "#{name}_uri(resource)"
152
+ )
153
+ end
154
+
155
+ def delete_link
156
+ link(
157
+ rel: "doc:delete",
158
+ desc: "Link to delete this #{name}",
159
+ method: "DELETE",
160
+ uri: "/#{plural_name}/5",
161
+ uri_helper: "#{name}_uri(resource)"
162
+ )
163
+ end
164
+
165
+ def link(rel:, method: "GET", desc:, uri:, uri_helper:)
166
+ <<~EOS.split("\n")
167
+ # Auto generated doc:
168
+ # #{desc}.
169
+ # Method: #{method}
170
+ #{example(method, uri)}
171
+ link :'#{rel}' do
172
+ #{uri_helper}
173
+ end
174
+ EOS
175
+ end
176
+
177
+ def example(method, uri)
178
+ method_args = ""
179
+ case method
180
+ when "POST"
181
+ method_args = "\n# -d@payload \\"
182
+ when "PUT"
183
+ method_args = "\n# -X PUT -d@payload \\"
184
+ when "DELETE"
185
+ method_args = "\n# -X DELETE \\"
186
+ end
187
+
188
+ <<~EOS.chomp
189
+ # Example:
190
+ # ```
191
+ # curl -H "Accept: application/json" \\
192
+ # -H "Authorization: abcdef" \\#{method_args}
193
+ # #{uri}
194
+ #```
195
+ EOS
196
+ end
197
+
198
+ def embeds
199
+ [:'doc:edit-form']
200
+ end
201
+
202
+ def embeds_with_doc
203
+ [
204
+ <<~EOS.split("\n")
205
+ # Auto generated doc:
206
+ # A form to edit this #{name}
207
+ embed :'doc:edit-form' do
208
+ resource.edit_form.tap do |form|
209
+ form.self_link = edit_#{name}_uri(resource)
210
+ form.href = #{name}_uri(resource)
211
+ end
212
+ end
213
+ EOS
214
+ ]
215
+ end
216
+
217
+ def collection_with_doc
218
+ <<~EOS.split("\n")
219
+ collection of: '#{plural_name}' do
220
+ link :self, #{plural_name}_uri
221
+ link :up, root_uri
222
+ curie(:doc) { doc_curie_uri('#{name}') }
223
+
224
+ embed :'doc:create-form' do
225
+ #{model_class_name}.create_form.tap do |form|
226
+ form.self_link = new_#{name}_uri
227
+ form.href = #{plural_name}_uri
228
+ end
229
+ end
230
+ end
231
+ EOS
232
+ end
233
+
234
+ def opts
235
+ {
236
+ name: name,
237
+ class_name: "#{name.capitalize}Serializer",
238
+ model_class_name: model_class_name,
239
+ policy_class_name: policy_class_name,
240
+ policy_name: "#{name}_policy",
241
+ attributes: attributes,
242
+ links: links,
243
+ embeds: embeds,
244
+ attributes_with_doc: attributes_with_doc,
245
+ curies_with_doc: curies_with_doc,
246
+ links_with_doc: links_with_doc,
247
+ embeds_with_doc: embeds_with_doc,
248
+ collection_with_doc: collection_with_doc,
249
+ }
250
+ end
251
+
252
+ def create_policy
253
+ policy_args = ["policy", name, *args[1..-1]]
254
+ Generator::Factory.create(*policy_args).call
255
+ end
256
+ end
257
+ end
258
+ end