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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de7f749b7b824c00e0a9e627168e9b14f1df6b64
|
4
|
+
data.tar.gz: 74e29cd67ba2d6d8e6f655c75f1f238f65bfe676
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22083943c64efc543caf45c6083e887443012cfc3cb1154f03a666b6d439b29cbac04c78d69492b225588caf56819ab9e9d467fbb96d235a5edb1e5938332a63
|
7
|
+
data.tar.gz: f455f61cbcf465eb890a1cacd12be95028d869df31416a7d75a51bb267ab16901cd3eb641d05e3d5608f7c6651a57bd83f8e8251aee0d700669b6fa5ba533eb1
|
data/lib/aws-record-generator.rb
CHANGED
@@ -14,7 +14,13 @@
|
|
14
14
|
require_relative 'generators/generated_attribute'
|
15
15
|
require_relative 'generators/secondary_index'
|
16
16
|
require_relative 'generators/test_helper'
|
17
|
+
require_relative 'generators/aws_record/base'
|
18
|
+
|
17
19
|
require_relative 'generators/aws_record/model/model_generator'
|
20
|
+
require_relative 'generators/aws_record/resource/resource_generator'
|
21
|
+
require_relative 'generators/aws_record/erb/erb_generator'
|
22
|
+
require_relative 'generators/aws_record/scaffold/scaffold_generator'
|
23
|
+
require_relative 'generators/aws_record/scaffold_controller/scaffold_controller_generator'
|
18
24
|
|
19
25
|
require 'aws-record'
|
20
26
|
require 'rails/railtie'
|
@@ -0,0 +1,80 @@
|
|
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
|
+
module AwsRecord
|
15
|
+
module Generators
|
16
|
+
class ActiveModel
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
def initialize(name)
|
20
|
+
@name = name
|
21
|
+
end
|
22
|
+
|
23
|
+
# GET index
|
24
|
+
def self.all(klass)
|
25
|
+
"#{klass}.scan"
|
26
|
+
end
|
27
|
+
|
28
|
+
# GET show
|
29
|
+
# GET edit
|
30
|
+
# PATCH/PUT update
|
31
|
+
# DELETE destroy
|
32
|
+
def self.find(klass, attrs)
|
33
|
+
hkey = attrs.select{|attr| attr.options[:hash_key]}[0]
|
34
|
+
rkey = attrs.select{|attr| attr.options[:range_key]}
|
35
|
+
rkey = !rkey.empty? ? rkey[0] : nil
|
36
|
+
|
37
|
+
if rkey
|
38
|
+
"""lambda {
|
39
|
+
id = params[:id].split('&').map{ |param| CGI.unescape(param) }
|
40
|
+
#{klass}.find(#{hkey.name}: id[0], #{rkey.name}: id[1])
|
41
|
+
}.call()"""
|
42
|
+
else
|
43
|
+
"#{klass}.find(#{hkey.name}: CGI.unescape(params[:id]))"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# GET new
|
48
|
+
# POST create
|
49
|
+
def self.build(klass, params = nil)
|
50
|
+
if params
|
51
|
+
"#{klass}.new(#{params})"
|
52
|
+
else
|
53
|
+
"#{klass}.new"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# POST create
|
58
|
+
def save
|
59
|
+
"#{name}.save"
|
60
|
+
end
|
61
|
+
|
62
|
+
# PATCH/PUT update
|
63
|
+
def update(params = nil)
|
64
|
+
"#{name}.update(#{params})"
|
65
|
+
end
|
66
|
+
|
67
|
+
# POST create
|
68
|
+
# PATCH/PUT update
|
69
|
+
def errors
|
70
|
+
"#{name}.errors"
|
71
|
+
end
|
72
|
+
|
73
|
+
# DELETE destroy
|
74
|
+
def destroy
|
75
|
+
"#{name}.delete!"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,228 @@
|
|
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
|
+
module AwsRecord
|
15
|
+
module Generators
|
16
|
+
class Base < Rails::Generators::NamedBase
|
17
|
+
argument :attributes, type: :array, default: [], banner: "field[:type][:opts]...", desc: "Describes the fields in the model"
|
18
|
+
check_class_collision
|
19
|
+
|
20
|
+
class_option :disable_mutation_tracking, type: :boolean, desc: "Disables dirty tracking"
|
21
|
+
class_option :timestamps, type: :boolean, desc: "Adds created, updated timestamps to the model"
|
22
|
+
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
|
23
|
+
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"
|
24
|
+
class_option :table_name, type: :string, banner: "model_table_name"
|
25
|
+
class_option :password_digest, type: :boolean, desc: "Whether to add a password_digest field to the model"
|
26
|
+
|
27
|
+
class_option :required, type: :array, default: [], banner: "field1...", desc: "A list of attributes that are required for an instance of the model"
|
28
|
+
class_option :length_validations, type: :hash, default: {}, banner: "field1:MIN-MAX...", desc: "Validations on the length of attributes in a model"
|
29
|
+
class_option :scaffold, type: :boolean, desc: "Adds helper methods that scaffolding uses"
|
30
|
+
|
31
|
+
attr_accessor :primary_read_units, :primary_write_units, :gsi_rw_units, :gsis, :required_attrs, :length_validations
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def initialize(args, *options)
|
36
|
+
options[0] << "--skip-table-config" if options[1][:behavior] == :revoke
|
37
|
+
@parse_errors = []
|
38
|
+
|
39
|
+
super
|
40
|
+
ensure_unique_fields
|
41
|
+
ensure_hkey
|
42
|
+
parse_gsis!
|
43
|
+
parse_table_config!
|
44
|
+
parse_validations!
|
45
|
+
|
46
|
+
if !@parse_errors.empty?
|
47
|
+
STDERR.puts "The following errors were encountered while trying to parse the given attributes"
|
48
|
+
STDERR.puts
|
49
|
+
STDERR.puts @parse_errors
|
50
|
+
STDERR.puts
|
51
|
+
|
52
|
+
abort("Please fix the errors before proceeding.")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_attributes!
|
57
|
+
|
58
|
+
self.attributes = (attributes || []).map do |attr|
|
59
|
+
begin
|
60
|
+
GeneratedAttribute.parse(attr)
|
61
|
+
rescue ArgumentError => e
|
62
|
+
@parse_errors << e
|
63
|
+
next
|
64
|
+
end
|
65
|
+
end
|
66
|
+
self.attributes = self.attributes.compact
|
67
|
+
|
68
|
+
if options['password_digest']
|
69
|
+
self.attributes << GeneratedAttribute.new("password_digest", :string_attr, :digest => true)
|
70
|
+
end
|
71
|
+
|
72
|
+
if options['timestamps']
|
73
|
+
self.attributes << GeneratedAttribute.parse("created:datetime:default_value{Time.now}")
|
74
|
+
self.attributes << GeneratedAttribute.parse("updated:datetime:default_value{Time.now}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def ensure_unique_fields
|
79
|
+
used_names = Set.new
|
80
|
+
duplicate_fields = []
|
81
|
+
|
82
|
+
self.attributes.each do |attr|
|
83
|
+
|
84
|
+
if used_names.include? attr.name
|
85
|
+
duplicate_fields << [:attribute, attr.name]
|
86
|
+
end
|
87
|
+
used_names.add attr.name
|
88
|
+
|
89
|
+
if attr.options.key? :database_attribute_name
|
90
|
+
raw_db_attr_name = attr.options[:database_attribute_name].delete('"') # db attribute names are wrapped with " to make template generation easier
|
91
|
+
|
92
|
+
if used_names.include? raw_db_attr_name
|
93
|
+
duplicate_fields << [:database_attribute_name, raw_db_attr_name]
|
94
|
+
end
|
95
|
+
|
96
|
+
used_names.add raw_db_attr_name
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if !duplicate_fields.empty?
|
101
|
+
duplicate_fields.each do |invalid_attr|
|
102
|
+
@parse_errors << ArgumentError.new("Found duplicated field name: #{invalid_attr[1]}, in attribute#{invalid_attr[0]}")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def ensure_hkey
|
108
|
+
uuid_member = nil
|
109
|
+
hkey_member = nil
|
110
|
+
rkey_member = nil
|
111
|
+
|
112
|
+
self.attributes.each do |attr|
|
113
|
+
if attr.options.key? :hash_key
|
114
|
+
if hkey_member
|
115
|
+
@parse_errors << ArgumentError.new("Redefinition of hash_key attr: #{attr.name}, original declaration of hash_key on: #{hkey_member.name}")
|
116
|
+
next
|
117
|
+
end
|
118
|
+
|
119
|
+
hkey_member = attr
|
120
|
+
elsif attr.options.key? :range_key
|
121
|
+
if rkey_member
|
122
|
+
@parse_errors << ArgumentError.new("Redefinition of range_key attr: #{attr.name}, original declaration of range_key on: #{hkey_member.name}")
|
123
|
+
next
|
124
|
+
end
|
125
|
+
|
126
|
+
rkey_member = attr
|
127
|
+
end
|
128
|
+
|
129
|
+
if attr.name.include? "uuid"
|
130
|
+
uuid_member = attr
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if !hkey_member
|
135
|
+
if uuid_member
|
136
|
+
uuid_member.options[:hash_key] = true
|
137
|
+
else
|
138
|
+
self.attributes.unshift GeneratedAttribute.parse("uuid:hkey")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def mutation_tracking_disabled?
|
144
|
+
options['disable_mutation_tracking']
|
145
|
+
end
|
146
|
+
|
147
|
+
def has_validations?
|
148
|
+
!@required_attrs.empty? || !@length_validations.empty?
|
149
|
+
end
|
150
|
+
|
151
|
+
def parse_table_config!
|
152
|
+
return unless options['table_config']
|
153
|
+
|
154
|
+
@primary_read_units, @primary_write_units = parse_rw_units("primary")
|
155
|
+
|
156
|
+
@gsi_rw_units = @gsis.map { |idx|
|
157
|
+
[idx.name, parse_rw_units(idx.name)]
|
158
|
+
}.to_h
|
159
|
+
|
160
|
+
options['table_config'].each do |config, rw_units|
|
161
|
+
if config == "primary"
|
162
|
+
next
|
163
|
+
else
|
164
|
+
gsi = @gsis.select { |idx| idx.name == config}
|
165
|
+
|
166
|
+
if gsi.empty?
|
167
|
+
@parse_errors << ArgumentError.new("Could not find a gsi declaration for #{config}")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def parse_rw_units(name)
|
174
|
+
if !options['table_config'].key? name
|
175
|
+
@parse_errors << ArgumentError.new("Please provide a table_config definition for #{name}")
|
176
|
+
else
|
177
|
+
rw_units = options['table_config'][name]
|
178
|
+
return rw_units.gsub(/[,.-]/, ':').split(':').reject { |s| s.empty? }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def parse_gsis!
|
183
|
+
@gsis = (options['gsi'] || []).map do |raw_idx|
|
184
|
+
begin
|
185
|
+
idx = SecondaryIndex.parse(raw_idx)
|
186
|
+
|
187
|
+
attributes = self.attributes.select { |attr| attr.name == idx.hash_key}
|
188
|
+
if attributes.empty?
|
189
|
+
@parse_errors << ArgumentError.new("Could not find attribute #{idx.hash_key} for gsi #{idx.name} hkey")
|
190
|
+
next
|
191
|
+
end
|
192
|
+
|
193
|
+
if idx.range_key
|
194
|
+
attributes = self.attributes.select { |attr| attr.name == idx.range_key}
|
195
|
+
if attributes.empty?
|
196
|
+
@parse_errors << ArgumentError.new("Could not find attribute #{idx.range_key} for gsi #{idx.name} rkey")
|
197
|
+
next
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
idx
|
202
|
+
rescue ArgumentError => e
|
203
|
+
@parse_errors << e
|
204
|
+
next
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
@gsis = @gsis.compact
|
209
|
+
end
|
210
|
+
|
211
|
+
def parse_validations!
|
212
|
+
@required_attrs = options['required']
|
213
|
+
@required_attrs.each do |val_attr|
|
214
|
+
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if !self.attributes.any? { |attr| attr.name == val_attr }
|
215
|
+
end
|
216
|
+
|
217
|
+
@length_validations = options['length_validations'].map do |val_attr, bounds|
|
218
|
+
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if !self.attributes.any? { |attr| attr.name == val_attr }
|
219
|
+
|
220
|
+
bounds = bounds.gsub(/[,.-]/, ':').split(':').reject { |s| s.empty? }
|
221
|
+
[val_attr, "#{bounds[0]}..#{bounds[1]}"]
|
222
|
+
end
|
223
|
+
@length_validations = @length_validations.to_h
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
@@ -0,0 +1,67 @@
|
|
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/erb"
|
15
|
+
require "rails/generators/resource_helpers"
|
16
|
+
|
17
|
+
module AwsRecord
|
18
|
+
module Generators
|
19
|
+
class ErbGenerator < Base
|
20
|
+
include Rails::Generators::ResourceHelpers
|
21
|
+
source_root File.expand_path('../templates', __FILE__)
|
22
|
+
|
23
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
24
|
+
|
25
|
+
def initialize(args, *options)
|
26
|
+
options[0] << "--skip-table-config"
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_root_folder
|
31
|
+
empty_directory File.join("app/views", controller_file_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def copy_view_files
|
35
|
+
available_views.each do |view|
|
36
|
+
formats.each do |format|
|
37
|
+
filename = filename_with_extensions(view, format)
|
38
|
+
template filename, File.join("app/views", controller_file_path, filename)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def available_views
|
46
|
+
%w(index edit show new _form)
|
47
|
+
end
|
48
|
+
|
49
|
+
def formats
|
50
|
+
[format]
|
51
|
+
end
|
52
|
+
|
53
|
+
def format
|
54
|
+
:html
|
55
|
+
end
|
56
|
+
|
57
|
+
def handler
|
58
|
+
:erb
|
59
|
+
end
|
60
|
+
|
61
|
+
def filename_with_extensions(name, file_format = format)
|
62
|
+
[name, file_format, handler].compact.join(".")
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<%%= form_with(model: <%= model_resource_name %>, local: true) do |form| %>
|
2
|
+
<%% if <%= singular_table_name %>.errors.any? %>
|
3
|
+
<div id="error_explanation">
|
4
|
+
<h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
|
5
|
+
|
6
|
+
<ul>
|
7
|
+
<%% <%= singular_table_name %>.errors.full_messages.each do |message| %>
|
8
|
+
<li><%%= message %></li>
|
9
|
+
<%% end %>
|
10
|
+
</ul>
|
11
|
+
</div>
|
12
|
+
<%% end %>
|
13
|
+
|
14
|
+
<% attributes.each do |attribute| -%>
|
15
|
+
<div class="field">
|
16
|
+
<% if attribute.password_digest? -%>
|
17
|
+
<%%= form.label :password %>
|
18
|
+
<%%= form.password_field :password %>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="field">
|
22
|
+
<%%= form.label :password_confirmation %>
|
23
|
+
<%%= form.password_field :password_confirmation %>
|
24
|
+
<% else -%>
|
25
|
+
<%%= form.label :<%= attribute.column_name %> %>
|
26
|
+
<%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %>
|
27
|
+
<% end -%>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<% end -%>
|
31
|
+
<div class="actions">
|
32
|
+
<%%= form.submit %>
|
33
|
+
</div>
|
34
|
+
<%% end %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<p id="notice"><%%= notice %></p>
|
2
|
+
|
3
|
+
<h1><%= plural_table_name.titleize %></h1>
|
4
|
+
|
5
|
+
<table>
|
6
|
+
<thead>
|
7
|
+
<tr>
|
8
|
+
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
9
|
+
<th><%= attribute.human_name %></th>
|
10
|
+
<% end -%>
|
11
|
+
<th colspan="3"></th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
|
15
|
+
<tbody>
|
16
|
+
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
|
17
|
+
<tr>
|
18
|
+
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
19
|
+
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
|
20
|
+
<% end -%>
|
21
|
+
<td><%%= link_to 'Show', <%= model_resource_name %> %></td>
|
22
|
+
<td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %></td>
|
23
|
+
<td><%%= link_to 'Destroy', <%= model_resource_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
24
|
+
</tr>
|
25
|
+
<%% end %>
|
26
|
+
</tbody>
|
27
|
+
</table>
|
28
|
+
|
29
|
+
<br>
|
30
|
+
|
31
|
+
<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_route_name %>_path %>
|