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.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-record-generator.rb +6 -0
  3. data/lib/generators/aws_record/active_model.rb +80 -0
  4. data/lib/generators/aws_record/base.rb +228 -0
  5. data/lib/generators/aws_record/erb/erb_generator.rb +67 -0
  6. data/lib/generators/aws_record/erb/templates/_form.html.erb.tt +34 -0
  7. data/lib/generators/aws_record/erb/templates/edit.html.erb.tt +6 -0
  8. data/lib/generators/aws_record/erb/templates/index.html.erb.tt +31 -0
  9. data/lib/generators/aws_record/erb/templates/new.html.erb.tt +5 -0
  10. data/lib/generators/aws_record/erb/templates/show.html.erb.tt +11 -0
  11. data/lib/generators/aws_record/model/USAGE +3 -0
  12. data/lib/generators/aws_record/model/model_generator.rb +7 -200
  13. data/lib/generators/aws_record/model/templates/model.rb.tt +47 -6
  14. data/lib/generators/aws_record/resource/USAGE +23 -0
  15. data/lib/generators/aws_record/resource/resource_generator.rb +31 -0
  16. data/lib/generators/aws_record/scaffold/USAGE +31 -0
  17. data/lib/generators/aws_record/scaffold/scaffold_generator.rb +56 -0
  18. data/lib/generators/aws_record/scaffold_controller/USAGE +15 -0
  19. data/lib/generators/aws_record/scaffold_controller/scaffold_controller_generator.rb +60 -0
  20. data/lib/generators/aws_record/scaffold_controller/templates/api_controller.rb.tt +63 -0
  21. data/lib/generators/aws_record/scaffold_controller/templates/controller.rb.tt +70 -0
  22. data/lib/generators/generated_attribute.rb +32 -0
  23. data/lib/generators/test_helper.rb +17 -6
  24. data/lib/tasks/table_config_migrate_task.rake +1 -1
  25. metadata +20 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88db52d888ba8ab309db4f229686bc1cb036397d
4
- data.tar.gz: 7ee3a11e989280135d8854d3eadf6acf04a7ac7d
3
+ metadata.gz: de7f749b7b824c00e0a9e627168e9b14f1df6b64
4
+ data.tar.gz: 74e29cd67ba2d6d8e6f655c75f1f238f65bfe676
5
5
  SHA512:
6
- metadata.gz: 47607097dc142faee512968f5845a2ef511c53fa11ece5f352804faac451f4895f2d1e54f168b0b538b5b1416cf9dbbd925f58d82563c308b98a56707e6580c7
7
- data.tar.gz: cb09166ed00e62c040732380b38ffde75a37065cb8755c2589f473ec12092d6b38513612c67c16681f4d73dd31f60c0638fcf3bf0632326c267b3531865dd2ca
6
+ metadata.gz: 22083943c64efc543caf45c6083e887443012cfc3cb1154f03a666b6d439b29cbac04c78d69492b225588caf56819ab9e9d467fbb96d235a5edb1e5938332a63
7
+ data.tar.gz: f455f61cbcf465eb890a1cacd12be95028d869df31416a7d75a51bb267ab16901cd3eb641d05e3d5608f7c6651a57bd83f8e8251aee0d700669b6fa5ba533eb1
@@ -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,6 @@
1
+ <h1>Editing <%= singular_table_name.titleize %></h1>
2
+
3
+ <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
4
+
5
+ <%%= link_to 'Show', @<%= singular_table_name %> %> |
6
+ <%%= link_to 'Back', <%= index_helper %>_path %>
@@ -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 %>