belt 0.2.12 → 0.2.14

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.
@@ -10,10 +10,14 @@ module Belt
10
10
  TEMPLATE_DIR = File.expand_path('../../templates/views', __dir__)
11
11
 
12
12
  def self.run(args)
13
+ force = args.delete('--force') || args.delete('-f')
14
+
13
15
  name = args.shift
14
16
  if name.nil? || name.empty?
15
- puts 'Usage: belt generate views <resource> [field:type ...]'
17
+ puts 'Usage: belt generate views <resource> [field:type ...] [options]'
16
18
  puts "\nGenerates React pages for all REST actions (index, show, new, edit)."
19
+ puts "\nOptions:"
20
+ puts ' --force, -f Overwrite existing files without prompting'
17
21
  puts "\nExamples:"
18
22
  puts ' belt generate views post title:string content:text status:string'
19
23
  puts ' belt generate views comment body:text author:string'
@@ -28,7 +32,7 @@ module Belt
28
32
  # If no fields provided, try to read from contracts.rb
29
33
  fields = read_schema_fields(name) if fields.empty?
30
34
 
31
- new(name, fields).generate
35
+ new(name, fields, force: force).generate
32
36
  end
33
37
 
34
38
  def self.read_schema_fields(name)
@@ -45,20 +49,37 @@ module Belt
45
49
 
46
50
  # Extract fields from model block
47
51
  if content =~ /model :#{singular} do\n(.*?)\n\s*end/m
48
- ::Regexp.last_match(1).scan(/field :(\w+), type: :(\w+)/).except('created_at', 'updated_at')
49
- .map do |n, t|
50
- {
51
- name: n, type: t
52
- }
52
+ block_content = ::Regexp.last_match(1)
53
+ timestamp_fields = %w[created_at updated_at]
54
+
55
+ # Support both formats:
56
+ # field :name, type: :string (legacy)
57
+ # string :name (current schema DSL)
58
+ dsl_types = %w[string text integer number boolean float date datetime]
59
+ dsl_pattern = /(?:#{dsl_types.join('|')}) :(\w+)/
60
+ fields = block_content.scan(/field :(\w+), type: :(\w+)/)
61
+ fields += block_content.scan(dsl_pattern).map do |match|
62
+ field_name = match[0]
63
+ # Extract type from the DSL method name on that line
64
+ type_match = block_content.match(/(\w+) :#{Regexp.escape(field_name)}/)
65
+ [field_name, type_match ? type_match[1] : 'string']
66
+ end
67
+
68
+ fields.filter_map do |n, t|
69
+ next if timestamp_fields.include?(n)
70
+
71
+ { name: n, type: t }
53
72
  end
54
73
  else
55
74
  []
56
75
  end
57
76
  end
58
77
 
59
- def initialize(name, fields)
78
+ def initialize(name, fields, force: false)
60
79
  @name = name.downcase.gsub(/[^a-z0-9_]/, '_')
61
80
  @fields = fields
81
+ @force = force
82
+ @overwrite_all = false
62
83
  @singular_name = Belt::Inflector.singularize(@name)
63
84
  @resource_name = Belt::Inflector.pluralize(@singular_name)
64
85
  @class_name = Belt::Inflector.classify(@singular_name)
@@ -97,8 +118,61 @@ module Belt
97
118
  def write_template(template_name, dest_path)
98
119
  template_path = File.join(TEMPLATE_DIR, template_name)
99
120
  content = ERB.new(File.read(template_path), trim_mode: '-').result(binding)
121
+ existed = File.exist?(dest_path)
122
+
123
+ if existed && !@force && !@overwrite_all
124
+ action = prompt_overwrite(dest_path)
125
+ case action
126
+ when :yes
127
+ # fall through to write
128
+ when :all
129
+ @overwrite_all = true
130
+ # fall through to write
131
+ when :no
132
+ puts " skip #{dest_path}"
133
+ return
134
+ when :quit
135
+ puts "\nAborted."
136
+ exit 1
137
+ end
138
+ end
139
+
100
140
  File.write(dest_path, content)
101
- puts " create #{dest_path}"
141
+ puts " #{existed ? 'overwrite' : 'create'} #{dest_path}"
142
+ end
143
+
144
+ def prompt_overwrite(path)
145
+ return :yes if @overwrite_all
146
+
147
+ print " conflict #{path}\n"
148
+ print " Overwrite #{path}? (enter \"h\" for help) [Ynaqh] "
149
+ $stdout.flush
150
+
151
+ loop do
152
+ answer = $stdin.gets&.strip&.downcase
153
+ case answer
154
+ when '', 'y', 'yes'
155
+ return :yes
156
+ when 'n', 'no'
157
+ return :no
158
+ when 'a', 'all'
159
+ @overwrite_all = true
160
+ return :all
161
+ when 'q', 'quit'
162
+ return :quit
163
+ when 'h', 'help'
164
+ puts ' Y - yes, overwrite this file'
165
+ puts ' n - no, skip this file'
166
+ puts ' a - all, overwrite this and all remaining files'
167
+ puts ' q - quit, abort the generator'
168
+ puts ' h - help, show this help'
169
+ print " Overwrite #{path}? (enter \"h\" for help) [Ynaqh] "
170
+ $stdout.flush
171
+ else
172
+ print ' Please enter Y, n, a, q, or h: '
173
+ $stdout.flush
174
+ end
175
+ end
102
176
  end
103
177
 
104
178
  def inject_routes
@@ -109,6 +183,12 @@ module Belt
109
183
  pages_dir = @resource_name
110
184
  plural_class = @plural_class_name || Belt::Inflector.camelize(@resource_name)
111
185
 
186
+ # Skip route injection if routes for this resource already exist
187
+ if content.include?("path=\"/#{@resource_name}\"")
188
+ puts " skip #{app_jsx} (routes already exist)"
189
+ return
190
+ end
191
+
112
192
  import_lines = [
113
193
  "import #{plural_class}Index from './pages/#{pages_dir}/#{plural_class}Index'",
114
194
  "import #{@class_name}Show from './pages/#{pages_dir}/#{@class_name}Show'",
data/lib/belt/cli.rb CHANGED
@@ -22,6 +22,7 @@ require_relative 'cli/contracts_command'
22
22
  require_relative 'cli/lambda_config_command'
23
23
  require_relative 'cli/tasks_command'
24
24
  require_relative 'cli/console_command'
25
+ require_relative 'cli/logs_command'
25
26
  require_relative 'cli/doctor_command'
26
27
  require_relative 'cli/plugin_command'
27
28
 
@@ -35,6 +36,7 @@ module Belt
35
36
  'contracts' => Belt::CLI::ContractsCommand,
36
37
  'lambda-config' => Belt::CLI::LambdaConfigCommand,
37
38
  %w[console c] => Belt::CLI::ConsoleCommand,
39
+ 'logs' => Belt::CLI::LogsCommand,
38
40
  %w[tasks --tasks -T] => Belt::CLI::TasksCommand,
39
41
  'setup' => Belt::CLI::SetupCommand,
40
42
  'doctor' => Belt::CLI::DoctorCommand,
@@ -116,6 +118,7 @@ module Belt
116
118
 
117
119
  console Start an interactive console (IRB)
118
120
  c Alias for console
121
+ logs [lambda] [-f] [-s 5m] [-e env] View Lambda function logs
119
122
  tasks [-g PATTERN] [-a] List available rake tasks
120
123
  -T [-g PATTERN] [-a] Alias for tasks
121
124
  setup state Create/select S3 state bucket
@@ -520,7 +520,7 @@ module Belt
520
520
 
521
521
  # SchemaBuilder captures request and response model definitions from contracts.rb
522
522
  class SchemaBuilder
523
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
523
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
524
524
 
525
525
  attr_reader :request_models, :response_models
526
526
 
@@ -557,7 +557,7 @@ module Belt
557
557
  end
558
558
 
559
559
  class RequestModelBuilder
560
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
560
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
561
561
 
562
562
  attr_reader :name, :fields
563
563
 
@@ -590,6 +590,7 @@ module Belt
590
590
 
591
591
  def map_type(dsl_type)
592
592
  case dsl_type
593
+ when :text, :date, :datetime then 'string'
593
594
  when :map then 'object'
594
595
  when :list then 'array'
595
596
  else dsl_type.to_s
@@ -598,7 +599,7 @@ module Belt
598
599
  end
599
600
 
600
601
  class ResponseModelBuilder
601
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
602
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
602
603
 
603
604
  attr_reader :name, :contexts, :fields
604
605
 
@@ -636,6 +637,7 @@ module Belt
636
637
 
637
638
  def map_type(dsl_type)
638
639
  case dsl_type
640
+ when :text, :date, :datetime then 'string'
639
641
  when :map then 'object'
640
642
  when :list then 'array'
641
643
  else dsl_type.to_s
@@ -644,7 +646,7 @@ module Belt
644
646
  end
645
647
 
646
648
  class ContextBuilder
647
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
649
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
648
650
 
649
651
  attr_reader :name, :fields
650
652
 
@@ -673,6 +675,7 @@ module Belt
673
675
 
674
676
  def map_type(dsl_type)
675
677
  case dsl_type
678
+ when :text, :date, :datetime then 'string'
676
679
  when :map then 'object'
677
680
  when :list then 'array'
678
681
  else dsl_type.to_s
data/lib/belt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belt
4
- VERSION = '0.2.12'
4
+ VERSION = '0.2.14'
5
5
  end
data/lib/belt.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'activeitem'
3
4
  require_relative 'belt/version'
4
5
  require_relative 'belt/root'
5
6
  require_relative 'belt/configuration'
@@ -4,55 +4,105 @@ require_relative 'application_controller'
4
4
 
5
5
  module <%= @module_name %>Controllers
6
6
  class <%= @class_name %>sController < ApplicationController
7
+ <% if @references.any? -%>
8
+ <% parent = @references.first -%>
9
+ <% parent_resource = Belt::Inflector.pluralize(parent[:referenced_model]) -%>
10
+ <% parent_class = Belt::Inflector.classify(parent[:referenced_model]) -%>
11
+ # GET /<%= parent_resource %>/:id/<%= @resource_name %>
12
+ def index
13
+ @<%= @resource_name %> = <%= @class_name %>.where(<%= parent[:referenced_model] %>_id: params[:<%= parent[:referenced_model] %>_id], index: '<%= Belt::Inflector.classify(parent[:referenced_model]) %>Index')
14
+ end
15
+
16
+ # POST /<%= parent_resource %>/:id/<%= @resource_name %>
17
+ def create
18
+ @<%= @singular_name %> = <%= @class_name %>.new(<%= (["#{parent[:referenced_model]}_id: params[:#{parent[:referenced_model]}_id]"] + @regular_fields.map { |f| "#{f[:name]}: params[:#{f[:name]}]" }).join(', ') %>)
19
+
20
+ if @<%= @singular_name %>.save
21
+ response_status :created
22
+ else
23
+ error_response(@<%= @singular_name %>.errors.full_messages.join(', '), :unprocessable_entity)
24
+ end
25
+ end
26
+
27
+ # GET /<%= parent_resource %>/:id/<%= @resource_name %>/:<%= @singular_name %>_id
28
+ def show
29
+ @<%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
30
+ return error_response('<%= @class_name %> not found', :not_found) unless @<%= @singular_name %>
31
+ end
32
+
33
+ # PUT /<%= parent_resource %>/:id/<%= @resource_name %>/:<%= @singular_name %>_id
34
+ def update
35
+ @<%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
36
+ return error_response('<%= @class_name %> not found', :not_found) unless @<%= @singular_name %>
37
+
38
+ attrs = {}
39
+ <% @regular_fields.each do |field| -%>
40
+ attrs[:<%= field[:name] %>] = params[:<%= field[:name] %>] if params.key?(:<%= field[:name] %>)
41
+ <% end -%>
42
+
43
+ if @<%= @singular_name %>.update(attrs)
44
+ # implicit response
45
+ else
46
+ error_response(@<%= @singular_name %>.errors.full_messages.join(', '), :unprocessable_entity)
47
+ end
48
+ end
49
+
50
+ # DELETE /<%= parent_resource %>/:id/<%= @resource_name %>/:<%= @singular_name %>_id
51
+ def destroy
52
+ <%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
53
+ return error_response('<%= @class_name %> not found', :not_found) unless <%= @singular_name %>
54
+
55
+ <%= @singular_name %>.destroy
56
+ head :no_content
57
+ end
58
+ <% else -%>
7
59
  # GET /<%= @resource_name %>
8
60
  def index
9
- <%= @resource_name %> = <%= @class_name %>.all
10
- success_response(<%= @resource_name %>: <%= @resource_name %>.map(&:to_h))
61
+ @<%= @resource_name %> = <%= @class_name %>.all
11
62
  end
12
63
 
13
64
  # POST /<%= @resource_name %>
14
65
  def create
15
- <%= @singular_name %> = <%= @class_name %>.new(<%= @fields.map { |f| "#{f[:name]}: params[:#{f[:name]}]" }.join(', ') %>)
66
+ @<%= @singular_name %> = <%= @class_name %>.new(<%= @regular_fields.map { |f| "#{f[:name]}: params[:#{f[:name]}]" }.join(', ') %>)
16
67
 
17
- if <%= @singular_name %>.save
18
- success_response(<%= @singular_name %>: <%= @singular_name %>.to_h)
68
+ if @<%= @singular_name %>.save
69
+ response_status :created
19
70
  else
20
- error_response(<%= @singular_name %>.errors.full_messages.join(', '), 422)
71
+ error_response(@<%= @singular_name %>.errors.full_messages.join(', '), :unprocessable_entity)
21
72
  end
22
73
  end
23
74
 
24
75
  # GET /<%= @resource_name %>/:<%= @singular_name %>_id
25
76
  def show
26
- <%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
27
- return error_response('<%= @class_name %> not found', 404) unless <%= @singular_name %>
28
-
29
- success_response(<%= @singular_name %>: <%= @singular_name %>.to_h)
77
+ @<%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
78
+ return error_response('<%= @class_name %> not found', :not_found) unless @<%= @singular_name %>
30
79
  end
31
80
 
32
81
  # PUT /<%= @resource_name %>/:<%= @singular_name %>_id
33
82
  def update
34
- <%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
35
- return error_response('<%= @class_name %> not found', 404) unless <%= @singular_name %>
83
+ @<%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
84
+ return error_response('<%= @class_name %> not found', :not_found) unless @<%= @singular_name %>
36
85
 
37
86
  attrs = {}
38
- <% @fields.each do |field| -%>
87
+ <% @regular_fields.each do |field| -%>
39
88
  attrs[:<%= field[:name] %>] = params[:<%= field[:name] %>] if params.key?(:<%= field[:name] %>)
40
89
  <% end -%>
41
90
 
42
- if <%= @singular_name %>.update(attrs)
43
- success_response(<%= @singular_name %>: <%= @singular_name %>.to_h)
91
+ if @<%= @singular_name %>.update(attrs)
92
+ # implicit response
44
93
  else
45
- error_response(<%= @singular_name %>.errors.full_messages.join(', '), 422)
94
+ error_response(@<%= @singular_name %>.errors.full_messages.join(', '), :unprocessable_entity)
46
95
  end
47
96
  end
48
97
 
49
98
  # DELETE /<%= @resource_name %>/:<%= @singular_name %>_id
50
99
  def destroy
51
100
  <%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
52
- return error_response('<%= @class_name %> not found', 404) unless <%= @singular_name %>
101
+ return error_response('<%= @class_name %> not found', :not_found) unless <%= @singular_name %>
53
102
 
54
103
  <%= @singular_name %>.destroy
55
- success_response(message: '<%= @class_name %> deleted')
104
+ head :no_content
56
105
  end
106
+ <% end -%>
57
107
  end
58
108
  end
@@ -1,18 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class <%= @class_name %> < ApplicationRecord
4
- <% @fields.each do |field| -%>
5
- attr_accessor :<%= field[:name] %>
4
+ <% @references.each do |ref| -%>
5
+ belongs_to :<%= ref[:referenced_model] %>
6
6
  <% end -%>
7
+ <% if @references.any? && (@regular_fields.any? || @references.any?) -%>
7
8
 
8
- def to_h
9
- {
10
- id: id,
11
- <% @fields.each do |field| -%>
12
- <%= field[:name] %>: <%= field[:name] %>,
13
9
  <% end -%>
14
- created_at: created_at,
15
- updated_at: updated_at
16
- }
17
- end
10
+ <% @references.each do |ref| -%>
11
+ attr_accessor :<%= ref[:referenced_model] %>_id
12
+ <% end -%>
13
+ <% @regular_fields.each do |field| -%>
14
+ attr_accessor :<%= field[:name] %>
15
+ <% end -%>
18
16
  end
@@ -2,6 +2,4 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- gem 'activeitem'
6
5
  gem 'belt'
7
- gem 'lambda_loadout'
@@ -12,3 +12,6 @@
12
12
  # Lambda build artifacts
13
13
  /lambda/vendor/
14
14
  /lambda/.bundle/
15
+
16
+ # Generated route manifests (belt routes --namespace)
17
+ /lambda/lib/routes/
@@ -1,15 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'belt'
4
- require 'activeitem'
5
4
 
6
5
  include Belt::LambdaHandler
7
6
 
8
- ActiveItem.configure do |config|
9
- config.table_prefix = ENV['APP_NAME']
10
- config.environment = ENV['ENVIRONMENT']
11
- end
12
-
13
7
  require_relative 'lib/routes/api_routes'
14
8
  <% @resources&.each do |r| -%>
15
9
  require_relative 'controllers/<%= @app_name %>/<%= r %>_controller'
@@ -3,16 +3,10 @@
3
3
  # Boot the application. Used by `belt console` and Lambda at runtime.
4
4
 
5
5
  require 'belt'
6
- require 'activeitem'
7
6
 
8
7
  ENV['APP_NAME'] ||= '<%= @app_name %>'
9
8
  ENV['AWS_REGION'] ||= 'us-east-1'
10
9
 
11
- ActiveItem.configure do |config|
12
- config.table_prefix = ENV['APP_NAME']
13
- config.environment = ENV['ENVIRONMENT']
14
- end
15
-
16
10
  # Load lib and models
17
11
  Dir[File.join(__dir__, '..', 'lib', '**', '*.rb')].sort.each { |f| require f }
18
12
 
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'activeitem'
4
-
5
3
  class ApplicationRecord < ActiveItem::Base
6
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: belt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stowzilla
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: '0.0'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.0.13
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,6 +26,9 @@ dependencies:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
28
  version: '0.0'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.0.13
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: activesupport
28
34
  requirement: !ruby/object:Gem::Requirement
@@ -89,6 +95,7 @@ files:
89
95
  - lib/belt/cli/generate_command.rb
90
96
  - lib/belt/cli/generator_registry.rb
91
97
  - lib/belt/cli/lambda_config_command.rb
98
+ - lib/belt/cli/logs_command.rb
92
99
  - lib/belt/cli/new_command.rb
93
100
  - lib/belt/cli/path_gem_materializer.rb
94
101
  - lib/belt/cli/plugin_command.rb