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