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
@@ -0,0 +1,5 @@
1
+ <h1>New <%= singular_table_name.titleize %></h1>
2
+
3
+ <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
4
+
5
+ <%%= link_to 'Back', <%= index_helper %>_path %>
@@ -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 < Rails::Generators::NamedBase
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
- @parse_errors = []
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 ensure_hkey
113
- uuid_member = nil
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 parse_gsis!
186
- @gsis = (options['gsi'] || []).map do |raw_idx|
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
- <%= "require 'active_model'
3
- " if has_validations? -%>
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
- <%= ' include ActiveModel::Validations
9
- ' if has_validations? -%>
10
- <%= ' disable_mutation_tracking
11
- ' if mutation_tracking_disabled? -%>
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