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,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