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