aws-record-generator 1.0.0.pre.1 → 1.0.0.pre.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aws-record-generator.rb +6 -0
- data/lib/generators/aws_record/active_model.rb +80 -0
- data/lib/generators/aws_record/base.rb +228 -0
- data/lib/generators/aws_record/erb/erb_generator.rb +67 -0
- data/lib/generators/aws_record/erb/templates/_form.html.erb.tt +34 -0
- data/lib/generators/aws_record/erb/templates/edit.html.erb.tt +6 -0
- data/lib/generators/aws_record/erb/templates/index.html.erb.tt +31 -0
- data/lib/generators/aws_record/erb/templates/new.html.erb.tt +5 -0
- data/lib/generators/aws_record/erb/templates/show.html.erb.tt +11 -0
- data/lib/generators/aws_record/model/USAGE +3 -0
- data/lib/generators/aws_record/model/model_generator.rb +7 -200
- data/lib/generators/aws_record/model/templates/model.rb.tt +47 -6
- data/lib/generators/aws_record/resource/USAGE +23 -0
- data/lib/generators/aws_record/resource/resource_generator.rb +31 -0
- data/lib/generators/aws_record/scaffold/USAGE +31 -0
- data/lib/generators/aws_record/scaffold/scaffold_generator.rb +56 -0
- data/lib/generators/aws_record/scaffold_controller/USAGE +15 -0
- data/lib/generators/aws_record/scaffold_controller/scaffold_controller_generator.rb +60 -0
- data/lib/generators/aws_record/scaffold_controller/templates/api_controller.rb.tt +63 -0
- data/lib/generators/aws_record/scaffold_controller/templates/controller.rb.tt +70 -0
- data/lib/generators/generated_attribute.rb +32 -0
- data/lib/generators/test_helper.rb +17 -6
- data/lib/tasks/table_config_migrate_task.rake +1 -1
- metadata +20 -4
@@ -0,0 +1,11 @@
|
|
1
|
+
<p id="notice"><%%= notice %></p>
|
2
|
+
|
3
|
+
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
4
|
+
<p>
|
5
|
+
<strong><%= attribute.human_name %>:</strong>
|
6
|
+
<%%= @<%= singular_table_name %>.<%= attribute.name %> %>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<% end -%>
|
10
|
+
<%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> |
|
11
|
+
<%%= link_to 'Back', <%= index_helper %>_path %>
|
@@ -11,6 +11,9 @@ Description:
|
|
11
11
|
|
12
12
|
Timestamps are not added by default but you can add them using the `--timestamps` flag
|
13
13
|
More information can be found at: https://github.com/awslabs/aws-record-generator/blob/master/README.md
|
14
|
+
|
15
|
+
You don't have to think up every attribute up front, but it helps to
|
16
|
+
sketch out a few so you can start working with the resource immediately.
|
14
17
|
|
15
18
|
Example:
|
16
19
|
rails generate aws_record:model Forum forum_uuid:hkey post_id:rkey post_title post_body tags:sset:default_value{Set.new} created_at:datetime:d_attr_name{PostCreatedAtTime} moderation:boolean:default_value{false}
|
@@ -13,218 +13,25 @@
|
|
13
13
|
|
14
14
|
require 'rails/generators'
|
15
15
|
require 'aws-record-generator'
|
16
|
+
require 'generators/aws_record/base'
|
16
17
|
|
17
18
|
module AwsRecord
|
18
19
|
module Generators
|
19
|
-
class ModelGenerator <
|
20
|
-
|
21
|
-
source_root File.expand_path('../templates', __FILE__)
|
22
|
-
argument :attributes, type: :array, default: [], banner: "field[:type][:opts]...", desc: "Describes the fields in the model"
|
23
|
-
check_class_collision
|
24
|
-
|
25
|
-
class_option :disable_mutation_tracking, type: :boolean, desc: "Disables dirty tracking"
|
26
|
-
class_option :timestamps, type: :boolean, desc: "Adds created, updated timestamps to the model"
|
27
|
-
class_option :table_config, type: :hash, default: {}, banner: "primary:R-W [SecondaryIndex1:R-W]...", desc: "Declares the r/w units for the model as well as any secondary indexes", :required => true
|
28
|
-
class_option :gsi, type: :array, default: [], banner: "name:hkey{field_name}[,rkey{field_name},proj_type{ALL|KEYS_ONLY|INCLUDE}]...", desc: "Allows for the declaration of secondary indexes"
|
29
|
-
|
30
|
-
class_option :required, type: :array, default: [], banner: "field1...", desc: "A list of attributes that are required for an instance of the model"
|
31
|
-
class_option :length_validations, type: :hash, default: {}, banner: "field1:MIN-MAX...", desc: "Validations on the length of attributes in a model"
|
32
|
-
|
33
|
-
attr_accessor :primary_read_units, :primary_write_units, :gsi_rw_units, :gsis, :required_attrs, :length_validations
|
34
|
-
|
35
|
-
def create_model
|
36
|
-
template "model.rb", File.join("app/models", class_path, "#{file_name}.rb")
|
37
|
-
end
|
38
|
-
|
39
|
-
def create_table_config
|
40
|
-
template "table_config.rb", File.join("db/table_config", class_path, "#{file_name}_config.rb")
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
20
|
+
class ModelGenerator < Base
|
44
21
|
|
45
22
|
def initialize(args, *options)
|
46
|
-
|
47
|
-
|
23
|
+
self.class.source_root File.expand_path('../templates', __FILE__)
|
48
24
|
super
|
49
|
-
ensure_unique_fields
|
50
|
-
ensure_hkey
|
51
|
-
parse_gsis!
|
52
|
-
parse_table_config!
|
53
|
-
parse_validations!
|
54
|
-
|
55
|
-
if !@parse_errors.empty?
|
56
|
-
STDERR.puts "The following errors were encountered while trying to parse the given attributes"
|
57
|
-
STDERR.puts
|
58
|
-
STDERR.puts @parse_errors
|
59
|
-
STDERR.puts
|
60
|
-
|
61
|
-
abort("Please fix the errors before proceeding.")
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def parse_attributes!
|
66
|
-
|
67
|
-
self.attributes = (attributes || []).map do |attr|
|
68
|
-
begin
|
69
|
-
GeneratedAttribute.parse(attr)
|
70
|
-
rescue ArgumentError => e
|
71
|
-
@parse_errors << e
|
72
|
-
next
|
73
|
-
end
|
74
|
-
end
|
75
|
-
self.attributes = self.attributes.compact
|
76
|
-
|
77
|
-
if options['timestamps']
|
78
|
-
self.attributes << GeneratedAttribute.parse("created:datetime:default_value{Time.now}")
|
79
|
-
self.attributes << GeneratedAttribute.parse("updated:datetime:default_value{Time.now}")
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def ensure_unique_fields
|
84
|
-
used_names = Set.new
|
85
|
-
duplicate_fields = []
|
86
|
-
|
87
|
-
self.attributes.each do |attr|
|
88
|
-
|
89
|
-
if used_names.include? attr.name
|
90
|
-
duplicate_fields << [:attribute, attr.name]
|
91
|
-
end
|
92
|
-
used_names.add attr.name
|
93
|
-
|
94
|
-
if attr.options.key? :database_attribute_name
|
95
|
-
raw_db_attr_name = attr.options[:database_attribute_name].delete('"') # db attribute names are wrapped with " to make template generation easier
|
96
|
-
|
97
|
-
if used_names.include? raw_db_attr_name
|
98
|
-
duplicate_fields << [:database_attribute_name, raw_db_attr_name]
|
99
|
-
end
|
100
|
-
|
101
|
-
used_names.add raw_db_attr_name
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
if !duplicate_fields.empty?
|
106
|
-
duplicate_fields.each do |invalid_attr|
|
107
|
-
@parse_errors << ArgumentError.new("Found duplicated field name: #{invalid_attr[1]}, in attribute#{invalid_attr[0]}")
|
108
|
-
end
|
109
|
-
end
|
110
25
|
end
|
111
26
|
|
112
|
-
def
|
113
|
-
|
114
|
-
hkey_member = nil
|
115
|
-
rkey_member = nil
|
116
|
-
|
117
|
-
self.attributes.each do |attr|
|
118
|
-
if attr.options.key? :hash_key
|
119
|
-
if hkey_member
|
120
|
-
@parse_errors << ArgumentError.new("Redefinition of hash_key attr: #{attr.name}, original declaration of hash_key on: #{hkey_member.name}")
|
121
|
-
next
|
122
|
-
end
|
123
|
-
|
124
|
-
hkey_member = attr
|
125
|
-
elsif attr.options.key? :range_key
|
126
|
-
if rkey_member
|
127
|
-
@parse_errors << ArgumentError.new("Redefinition of range_key attr: #{attr.name}, original declaration of range_key on: #{hkey_member.name}")
|
128
|
-
next
|
129
|
-
end
|
130
|
-
|
131
|
-
rkey_member = attr
|
132
|
-
end
|
133
|
-
|
134
|
-
if attr.name.include? "uuid"
|
135
|
-
uuid_member = attr
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
if !hkey_member
|
140
|
-
if uuid_member
|
141
|
-
uuid_member.options[:hash_key] = true
|
142
|
-
else
|
143
|
-
self.attributes.unshift GeneratedAttribute.parse("uuid:hkey")
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def mutation_tracking_disabled?
|
149
|
-
options['disable_mutation_tracking']
|
150
|
-
end
|
151
|
-
|
152
|
-
def has_validations?
|
153
|
-
!@required_attrs.empty? || !@length_validations.empty?
|
154
|
-
end
|
155
|
-
|
156
|
-
def parse_table_config!
|
157
|
-
@primary_read_units, @primary_write_units = parse_rw_units("primary")
|
158
|
-
|
159
|
-
@gsi_rw_units = @gsis.map { |idx|
|
160
|
-
[idx.name, parse_rw_units(idx.name)]
|
161
|
-
}.to_h
|
162
|
-
|
163
|
-
options['table_config'].each do |config, rw_units|
|
164
|
-
if config == "primary"
|
165
|
-
next
|
166
|
-
else
|
167
|
-
gsi = @gsis.select { |idx| idx.name == config}
|
168
|
-
|
169
|
-
if gsi.empty?
|
170
|
-
@parse_errors << ArgumentError.new("Could not find a gsi declaration for #{config}")
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def parse_rw_units(name)
|
177
|
-
if !options['table_config'].key? name
|
178
|
-
@parse_errors << ArgumentError.new("Please provide a table_config definition for #{name}")
|
179
|
-
else
|
180
|
-
rw_units = options['table_config'][name]
|
181
|
-
return rw_units.gsub(/[,.-]/, ':').split(':').reject { |s| s.empty? }
|
182
|
-
end
|
27
|
+
def create_model
|
28
|
+
template "model.rb", File.join("app/models", class_path, "#{file_name}.rb")
|
183
29
|
end
|
184
30
|
|
185
|
-
def
|
186
|
-
|
187
|
-
begin
|
188
|
-
idx = SecondaryIndex.parse(raw_idx)
|
189
|
-
|
190
|
-
attributes = self.attributes.select { |attr| attr.name == idx.hash_key}
|
191
|
-
if attributes.empty?
|
192
|
-
@parse_errors << ArgumentError.new("Could not find attribute #{idx.hash_key} for gsi #{idx.name} hkey")
|
193
|
-
next
|
194
|
-
end
|
195
|
-
|
196
|
-
if idx.range_key
|
197
|
-
attributes = self.attributes.select { |attr| attr.name == idx.range_key}
|
198
|
-
if attributes.empty?
|
199
|
-
@parse_errors << ArgumentError.new("Could not find attribute #{idx.range_key} for gsi #{idx.name} rkey")
|
200
|
-
next
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
idx
|
205
|
-
rescue ArgumentError => e
|
206
|
-
@parse_errors << e
|
207
|
-
next
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
@gsis = @gsis.compact
|
31
|
+
def create_table_config
|
32
|
+
template "table_config.rb", File.join("db/table_config", class_path, "#{file_name}_config.rb") if options["table_config"]
|
212
33
|
end
|
213
34
|
|
214
|
-
def parse_validations!
|
215
|
-
@required_attrs = options['required']
|
216
|
-
@required_attrs.each do |val_attr|
|
217
|
-
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if !self.attributes.any? { |attr| attr.name == val_attr }
|
218
|
-
end
|
219
|
-
|
220
|
-
@length_validations = options['length_validations'].map do |val_attr, bounds|
|
221
|
-
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if !self.attributes.any? { |attr| attr.name == val_attr }
|
222
|
-
|
223
|
-
bounds = bounds.gsub(/[,.-]/, ':').split(':').reject { |s| s.empty? }
|
224
|
-
[val_attr, "#{bounds[0]}..#{bounds[1]}"]
|
225
|
-
end
|
226
|
-
@length_validations = @length_validations.to_h
|
227
|
-
end
|
228
35
|
end
|
229
36
|
end
|
230
37
|
end
|
@@ -1,14 +1,23 @@
|
|
1
1
|
require 'aws-record'
|
2
|
-
|
3
|
-
|
2
|
+
<% if has_validations? -%>
|
3
|
+
require 'active_model'
|
4
|
+
<% end -%>
|
4
5
|
|
5
6
|
<% module_namespacing do -%>
|
6
7
|
class <%= class_name %>
|
7
8
|
include Aws::Record
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
<% if options.key? :scaffold -%>
|
10
|
+
extend ActiveModel::Naming
|
11
|
+
<% end -%>
|
12
|
+
<% if has_validations? -%>
|
13
|
+
include ActiveModel::Validations
|
14
|
+
<% end -%>
|
15
|
+
<% if options.key? :password_digest -%>
|
16
|
+
include ActiveModel::SecurePassword
|
17
|
+
<% end -%>
|
18
|
+
<% if mutation_tracking_disabled? -%>
|
19
|
+
disable_mutation_tracking
|
20
|
+
<% end -%>
|
12
21
|
|
13
22
|
<% attributes.each do |attribute| -%>
|
14
23
|
<%= attribute.type %> :<%= attribute.name %><% *opts, last_opt = attribute.options.to_a %><%= ', ' if last_opt %><% opts.each do |opt| %><%= opt[0] %>: <%= opt[1] %>, <% end %><% if last_opt %><%= last_opt[0] %>: <%= last_opt[1] %><% end %>
|
@@ -29,5 +38,37 @@ class <%= class_name %>
|
|
29
38
|
<% length_validations.each do |attribute, validation| -%>
|
30
39
|
validates_length_of :<%= attribute %>, within: <%= validation %>
|
31
40
|
<% end -%>
|
41
|
+
<% if options.key? :scaffold -%>
|
42
|
+
|
43
|
+
# Scaffolding helpers
|
44
|
+
def initialize(args = {})
|
45
|
+
super
|
46
|
+
@errors = ActiveModel::Errors.new(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :errors
|
50
|
+
|
51
|
+
def to_model
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_param
|
56
|
+
return nil unless persisted?
|
57
|
+
|
58
|
+
hkey = public_send(self.class.hash_key)
|
59
|
+
if self.class.range_key
|
60
|
+
rkey = public_send(self.class.range_key)
|
61
|
+
"#{CGI.escape(hkey)}&#{CGI.escape(rkey)}"
|
62
|
+
else
|
63
|
+
"#{CGI.escape(hkey)}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
<% end -%>
|
67
|
+
<% if options['table_name'] -%>
|
68
|
+
set_table_name "<%= options['table_name'] %>"
|
69
|
+
<% end -%>
|
70
|
+
<% if options.key? :password_digest -%>
|
71
|
+
has_secure_password
|
72
|
+
<% end -%>
|
32
73
|
end
|
33
74
|
<% end -%>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Description:
|
2
|
+
Stubs out a new resource including an empty model and controller suitable
|
3
|
+
for a RESTful, resource-oriented application. Pass the singular model name,
|
4
|
+
either CamelCased or under_scored, as the first argument, and an optional
|
5
|
+
list of attribute pairs.
|
6
|
+
|
7
|
+
Attribute pairs are field:type arguments specifying the
|
8
|
+
model's attributes. Timestamps are added by default, so you don't have to
|
9
|
+
specify them by hand as 'created_at:datetime updated_at:datetime'.
|
10
|
+
|
11
|
+
You don't have to think up every attribute up front, but it helps to
|
12
|
+
sketch out a few so you can start working with the model immediately.
|
13
|
+
|
14
|
+
This generator invokes your configured ORM and test framework, besides
|
15
|
+
creating helpers and add routes to config/routes.rb.
|
16
|
+
|
17
|
+
Unlike the scaffold generator, the resource generator does not create
|
18
|
+
views or add any methods to the generated controller.
|
19
|
+
|
20
|
+
Examples:
|
21
|
+
`rails generate resource post` # no attributes
|
22
|
+
`rails generate resource post title:string body:text published:boolean`
|
23
|
+
`rails generate resource purchase order_id:integer amount:decimal`
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not
|
4
|
+
# use this file except in compliance with the License. A copy of the License is
|
5
|
+
# located at
|
6
|
+
#
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
8
|
+
#
|
9
|
+
# or in the "license" file accompanying this file. This file is distributed on
|
10
|
+
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
11
|
+
# or implied. See the License for the specific language governing permissions
|
12
|
+
# and limitations under the License.
|
13
|
+
|
14
|
+
require "rails/generators/rails/resource_route/resource_route_generator"
|
15
|
+
require "rails/generators/resource_helpers"
|
16
|
+
require 'generators/aws_record/active_model'
|
17
|
+
|
18
|
+
module AwsRecord
|
19
|
+
module Generators
|
20
|
+
class ResourceGenerator < ModelGenerator
|
21
|
+
include Rails::Generators::ResourceHelpers
|
22
|
+
|
23
|
+
hook_for :resource_route, in: :rails, required: true
|
24
|
+
|
25
|
+
private
|
26
|
+
def orm_class
|
27
|
+
@orm_class = AwsRecord::Generators::ActiveModel
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Description:
|
2
|
+
Scaffolds an entire resource, from model and migration to controller and
|
3
|
+
views, along with a full test suite. The resource is ready to use as a
|
4
|
+
starting point for your RESTful, resource-oriented application.
|
5
|
+
|
6
|
+
Pass the name of the model (preferably in singular form), and an optional list of attributes
|
7
|
+
|
8
|
+
Attributes are declarations of the fields that you wish to store within a model. You can pass
|
9
|
+
a type and list of options for each attribtue in the form: `name:type:options` if you do not provide
|
10
|
+
a type, it is assumed that the attribute is of type `string_attr`
|
11
|
+
|
12
|
+
Each model should have an hkey, if one is not present a `uuid:hkey` will be created for you.
|
13
|
+
|
14
|
+
Timestamps are not added by default but you can add them using the `--timestamps` flag
|
15
|
+
More information can be found at: https://github.com/awslabs/aws-record-generator/blob/master/README.md
|
16
|
+
|
17
|
+
You don't have to think up every attribute up front, but it helps to
|
18
|
+
sketch out a few so you can start working with the resource immediately.
|
19
|
+
|
20
|
+
For example, 'scaffold post title:hkey body published:boolean' gives
|
21
|
+
you a model with those three attributes, a controller that handles
|
22
|
+
the create/show/update/destroy, forms to create and edit your posts, and
|
23
|
+
an index that lists them all, as well as a resources :posts declaration
|
24
|
+
in config/routes.rb.
|
25
|
+
|
26
|
+
If you want to remove all the generated files, run
|
27
|
+
'rails destroy scaffold ModelName'.
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
`rails generate scaffold post title:hkey body published:boolean`
|
31
|
+
`rails generate scaffold purchase amount:float:hkey tracking_id:integer:rkey`
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not
|
4
|
+
# use this file except in compliance with the License. A copy of the License is
|
5
|
+
# located at
|
6
|
+
#
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
8
|
+
#
|
9
|
+
# or in the "license" file accompanying this file. This file is distributed on
|
10
|
+
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
11
|
+
# or implied. See the License for the specific language governing permissions
|
12
|
+
# and limitations under the License.
|
13
|
+
|
14
|
+
require "rails/generators/rails/scaffold/scaffold_generator"
|
15
|
+
require "generators/aws_record/resource/resource_generator"
|
16
|
+
|
17
|
+
module AwsRecord
|
18
|
+
module Generators
|
19
|
+
class ScaffoldGenerator < ResourceGenerator
|
20
|
+
source_root File.expand_path('../../model/templates', __FILE__)
|
21
|
+
|
22
|
+
remove_class_option :orm
|
23
|
+
remove_class_option :actions
|
24
|
+
|
25
|
+
class_option :api, type: :boolean
|
26
|
+
class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets"
|
27
|
+
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
|
28
|
+
class_option :assets, type: :boolean
|
29
|
+
class_option :resource_route, type: :boolean
|
30
|
+
class_option :scaffold_stylesheet, type: :boolean
|
31
|
+
|
32
|
+
def handle_skip
|
33
|
+
@options = @options.merge(stylesheets: false) unless options[:assets]
|
34
|
+
@options = @options.merge(stylesheet_engine: false) unless options[:stylesheets] && options[:scaffold_stylesheet]
|
35
|
+
end
|
36
|
+
|
37
|
+
hook_for :scaffold_controller, in: :aws_record, required: true
|
38
|
+
|
39
|
+
hook_for :assets, in: :rails do |assets|
|
40
|
+
invoke assets, [controller_name]
|
41
|
+
end
|
42
|
+
|
43
|
+
hook_for :stylesheet_engine, in: :rails do |stylesheet_engine|
|
44
|
+
if behavior == :invoke
|
45
|
+
invoke stylesheet_engine, [controller_name]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def initialize(args, *options)
|
51
|
+
options[0] << "--scaffold"
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|