aws-record-generator 1.0.0.pre.1 → 1.0.0.pre.2
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
- 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
|