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