ru.Bee 1.5.4 → 1.7.0

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rubee +6 -295
  3. data/lib/app/views/index.html +1 -1
  4. data/lib/config/base_configuration.rb +25 -7
  5. data/lib/db/create_addresses.rb +17 -0
  6. data/lib/inits/charged_hash.rb +16 -0
  7. data/lib/inits/charged_string.rb +12 -0
  8. data/lib/js/app.js +3 -3
  9. data/lib/package.json +1 -1
  10. data/lib/rubee/async/fiber_queue.rb +27 -0
  11. data/lib/rubee/async/thread_async.rb +1 -1
  12. data/lib/rubee/async/thread_pool.rb +32 -34
  13. data/lib/rubee/autoload.rb +86 -0
  14. data/lib/rubee/cli/attach.rb +124 -0
  15. data/lib/rubee/cli/command.rb +41 -0
  16. data/lib/rubee/cli/console.rb +39 -0
  17. data/lib/rubee/cli/db.rb +105 -0
  18. data/lib/rubee/cli/generate.rb +33 -0
  19. data/lib/rubee/cli/project.rb +124 -0
  20. data/lib/rubee/cli/react.rb +28 -0
  21. data/lib/rubee/cli/routes.rb +18 -0
  22. data/lib/rubee/cli/server.rb +52 -0
  23. data/lib/rubee/cli/test.rb +24 -0
  24. data/lib/rubee/cli/version.rb +15 -0
  25. data/lib/rubee/configuration.rb +83 -0
  26. data/lib/rubee/controllers/base_controller.rb +12 -6
  27. data/lib/rubee/controllers/extensions/auth_tokenable.rb +2 -2
  28. data/lib/rubee/extensions/hookable.rb +9 -2
  29. data/lib/rubee/generator.rb +160 -0
  30. data/lib/rubee/logger.rb +83 -0
  31. data/lib/rubee/models/database_objectable.rb +1 -1
  32. data/lib/rubee/models/sequel_object.rb +3 -3
  33. data/lib/rubee/router.rb +40 -0
  34. data/lib/rubee.rb +13 -317
  35. data/lib/tests/async/thread_async_test.rb +9 -5
  36. data/lib/tests/cli/attach_test.rb +36 -0
  37. data/lib/tests/{auth_tokenable_test.rb → controllers/auth_tokenable_test.rb} +2 -2
  38. data/lib/tests/controllers/base_controller_test.rb +23 -0
  39. data/lib/tests/controllers/hookable_test.rb +220 -0
  40. data/lib/tests/{rubeeapp_test.rb → controllers/rubeeapp_test.rb} +3 -2
  41. data/lib/tests/example_models/address.rb +5 -0
  42. data/lib/tests/example_models/user.rb +1 -0
  43. data/lib/tests/logger_test.rb +76 -0
  44. data/lib/tests/{account_model_test.rb → models/account_model_test.rb} +1 -1
  45. data/lib/tests/{comment_model_test.rb → models/comment_model_test.rb} +13 -1
  46. data/lib/tests/models/db_objectable_test.rb +21 -0
  47. data/lib/tests/models/seralizable_test.rb +36 -0
  48. data/lib/tests/{user_model_test.rb → models/user_model_test.rb} +32 -1
  49. data/lib/tests/rubee_attach_test.rb +0 -0
  50. data/lib/tests/test.db +0 -0
  51. data/lib/tests/test_helper.rb +20 -2
  52. data/readme.md +174 -15
  53. metadata +34 -9
  54. data/lib/app/views/apples_.erb +0 -1
  55. data/lib/app/views/s_.erb +0 -1
  56. /data/lib/app/views/{app.tsx → App.tsx} +0 -0
@@ -68,12 +68,18 @@ module Rubee
68
68
  in :not_found
69
69
  [404, { 'content-type' => 'text/plain' }, ['Route not found']]
70
70
  else # rendering erb view is a default behavior
71
- view_file_name = self.class.name.split('Controller').first.downcase
71
+ # TODO: refactor
72
+ view_file_name = self.class.name.split('Controller').first.gsub('::', '_').downcase
72
73
  erb_file = render_view ? render_view.to_s : "#{view_file_name}_#{@route[:action]}"
73
74
  lib = Rubee::PROJECT_NAME == 'rubee' ? 'lib/' : ''
74
- view = render_template(erb_file, { object:, **(options[:locals] || {}) })
75
-
76
- whole_erb = if File.exist?(layout_path = "#{lib}app/views/#{options[:layout] || 'layout'}.erb")
75
+ path_parts = self.class.instance_method(@route[:action]).source_location[0].split('/').reverse
76
+ controller_index = path_parts.find_index { |part| part == 'controllers' }
77
+ app_name = path_parts[controller_index + 1]
78
+ view = render_template(erb_file, { object:, **(options[:locals] || {}) }, app_name:)
79
+ # Since controller sits in the controllers folder we can get parent folder of it and pull out name of the app
80
+ app_name_prefix = app_name == 'app' ? '' : "#{app_name}_"
81
+ layout_path = "#{lib}#{app_name}/views/#{app_name_prefix}#{options[:layout] || 'layout'}.erb"
82
+ whole_erb = if File.exist?(layout_path)
77
83
  context = Object.new
78
84
  context.define_singleton_method(:_yield_template) { view }
79
85
  layout = File.read(layout_path)
@@ -86,9 +92,9 @@ module Rubee
86
92
  end
87
93
  end
88
94
 
89
- def render_template(file_name, locals = {})
95
+ def render_template(file_name, locals = {}, **options)
90
96
  lib = Rubee::PROJECT_NAME == 'rubee' ? 'lib/' : ''
91
- path = "#{lib}app/views/#{file_name}.erb"
97
+ path = "#{lib}#{options[:app_name] || 'app'}/views/#{file_name}.erb"
92
98
  erb_template = ERB.new(File.read(path))
93
99
 
94
100
  erb_template.result(binding)
@@ -3,8 +3,8 @@ require 'date'
3
3
 
4
4
  module Rubee
5
5
  module AuthTokenable
6
- KEY = "secret#{Date.today}".freeze # Feel free to cusomtize it
7
- EXPIRE = 3600 # 1 hour
6
+ KEY = "secret#{Date.today}".freeze unless defined?(KEY) # Feel free to cusomtize it
7
+ EXPIRE = 3600 unless defined?(EXPIRE)
8
8
 
9
9
  def self.included(base)
10
10
  base.include(Middlewarable)
@@ -39,9 +39,16 @@ module Rubee
39
39
  define_method(method) do |*args, &block|
40
40
  if conditions_met?(options[:if], options[:unless])
41
41
  if handler.respond_to?(:call)
42
- handler.call { super(*args, &block) }
42
+ result = nil
43
+ handler.call do
44
+ result = super(*args, &block)
45
+ end
46
+
47
+ result
43
48
  else
44
- send(handler) { super(*args, &block) }
49
+ return send(handler) do
50
+ super(*args, &block)
51
+ end
45
52
  end
46
53
  else
47
54
  super(*args, &block)
@@ -0,0 +1,160 @@
1
+ module Rubee
2
+ class Generator
3
+ require_relative '../inits/charged_string'
4
+ using ChargedString
5
+
6
+ def initialize(model_name, model_attributes, controller_name, action_name, **options)
7
+ @model_name = model_name&.downcase
8
+ @model_attributes = model_attributes || []
9
+ @base_name = controller_name.to_s.gsub('Controller', '').downcase.to_s
10
+ color_puts("base_name: #{@base_name}", color: :gray)
11
+ @plural_name = @base_name.plural? ? @base_name : @base_name.pluralize
12
+ @action_name = action_name
13
+ @react = options[:react] || {}
14
+ @app_name = options[:app_name] || :app
15
+ @namespace = @app_name == :app ? '' : "#{@app_name.camelize}::"
16
+ end
17
+
18
+ def call
19
+ generate_model if @model_name
20
+ generate_db_file if @model_name
21
+ generate_controller if @base_name && @action_name
22
+ generate_view if @base_name
23
+ end
24
+
25
+ private
26
+
27
+ def generate_model
28
+ model_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "#{@app_name.to_s.snakeize}/models/#{@model_name}.rb")
29
+ if File.exist?(model_file)
30
+ puts "Model #{@model_name} already exists. Remove it if you want to regenerate"
31
+ return
32
+ end
33
+
34
+ content = <<~RUBY
35
+ class #{@namespace}#{@model_name.camelize} < Rubee::SequelObject
36
+ #{'attr_accessor ' + @model_attributes.map { |hash| ":#{hash[:name]}" }.join(', ') unless @model_attributes.empty?}
37
+ end
38
+ RUBY
39
+
40
+ File.open(model_file, 'w') { |file| file.write(content) }
41
+ color_puts("Model #{@model_name} created", color: :green)
42
+ end
43
+
44
+ def generate_controller
45
+ controller_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "#{@app_name}/controllers/#{@base_name}_controller.rb")
46
+ if File.exist?(controller_file)
47
+ puts "Controller #{@base_name} already exists. Remove it if you want to regenerate"
48
+ return
49
+ end
50
+
51
+ content = <<~RUBY
52
+ class #{@namespace}#{@base_name.camelize}Controller < Rubee::BaseController
53
+ def #{@action_name}
54
+ response_with
55
+ end
56
+ end
57
+ RUBY
58
+
59
+ File.open(controller_file, 'w') { |file| file.write(content) }
60
+ color_puts("Controller #{@base_name} created", color: :green)
61
+ end
62
+
63
+ def generate_view
64
+ prefix = @namespace == "" ? "" : "#{@app_name.snakeize}_"
65
+ if @react[:view_name]
66
+ view_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "#{@app_name}/views/#{@react[:view_name]}")
67
+ content = <<~JS
68
+ import React, { useEffect, useState } from "react";
69
+ // 1. Add your logic that fetches data
70
+ // 2. Do not forget to add respective react route
71
+ export function #{@react[:view_name].gsub(/\.(.*)+$/, '').camelize}() {
72
+
73
+ return (
74
+ <div>
75
+ <h2>#{@react[:view_name].gsub(/\.(.*)+$/, '').camelize} view</h2>
76
+ </div>
77
+ );
78
+ }
79
+ JS
80
+ else # erb
81
+ view_file = File.join(
82
+ Rubee::APP_ROOT, Rubee::LIB,
83
+ "#{@app_name}/views/#{prefix}#{@plural_name}_#{@action_name}.erb"
84
+ )
85
+ content = <<~ERB
86
+ <h1>#{prefix}#{@plural_name}_#{@action_name} View</h1>
87
+ ERB
88
+ end
89
+
90
+ name = @react[:view_name] || "#{prefix}#{@plural_name}_#{@action_name}"
91
+
92
+ if File.exist?(view_file)
93
+ puts "View #{name} already exists. Remove it if you want to regenerate"
94
+ return
95
+ end
96
+
97
+ File.open(view_file, 'w') { |file| file.write(content) }
98
+ color_puts("View #{name} created", color: :green)
99
+ end
100
+
101
+ def generate_db_file
102
+ table_name = @namespace == "" ? @plural_name : "#{@namespace.snakeize}_#{@plural_name}"
103
+ db_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "db/create_#{table_name}.rb")
104
+ if File.exist?(db_file)
105
+ puts "DB file for #{table_name} already exists. Remove it if you want to regenerate"
106
+ return
107
+ end
108
+
109
+ content = <<~RUBY
110
+ class Create#{table_name.camelize}
111
+ def call
112
+ return if Rubee::SequelObject::DB.tables.include?(:#{table_name})
113
+
114
+ Rubee::SequelObject::DB.create_table(:#{table_name}) do
115
+ #{@model_attributes.map { |attribute| generate_sequel_schema(attribute) }.join("\n\t\t\t")}
116
+ end
117
+ end
118
+ end
119
+ RUBY
120
+
121
+ File.open(db_file, 'w') { |file| file.write(content) }
122
+ color_puts("DB file for #{table_name} created", color: :green)
123
+ end
124
+
125
+ def generate_sequel_schema(attribute)
126
+ type = attribute[:type]
127
+ name = if attribute[:name].is_a?(Array)
128
+ attribute[:name].map { |nom| ":#{nom}" }.join(", ").prepend('[') + ']'
129
+ else
130
+ ":#{attribute[:name]}"
131
+ end
132
+ table = attribute[:table] || 'replace_with_table_name'
133
+ options = attribute[:options] || {}
134
+
135
+ lookup_hash = {
136
+ primary: "primary_key #{name}",
137
+ string: "String #{name}",
138
+ text: "String #{name}, text: true",
139
+ integer: "Integer #{name}",
140
+ date: "Date #{name}",
141
+ datetime: "DateTime #{name}",
142
+ time: "Time #{name}",
143
+ boolean: "TrueClass #{name}",
144
+ bigint: "Bignum #{name}",
145
+ decimal: "BigDecimal #{name}",
146
+ foreign_key: "foreign_key #{name}, :#{table}",
147
+ index: "index #{name}",
148
+ unique: "unique #",
149
+ }
150
+
151
+ statement = lookup_hash[type.to_sym]
152
+
153
+ options.keys.each do |key|
154
+ statement += ", #{key}: '#{options[key]}'"
155
+ end
156
+
157
+ statement
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,83 @@
1
+ module Rubee
2
+ class Logger
3
+ class << self
4
+ def warn(message:, **options, &block)
5
+ out.warn(message:, **options, &block)
6
+ end
7
+
8
+ def error(message:, **options, &block)
9
+ out.error(message:, **options, &block)
10
+ end
11
+
12
+ def critical(message:, **options, &block)
13
+ out.critical(message:, **options, &block)
14
+ end
15
+
16
+ def info(message:, **options, &block)
17
+ out.info(message:, **options, &block)
18
+ end
19
+
20
+ def debug(object:, **options, &block)
21
+ out.debug(object:, **options, &block)
22
+ end
23
+
24
+ def out
25
+ Rubee::Configuration.get_logger || Stdout
26
+ end
27
+ end
28
+ end
29
+
30
+ class Stdout
31
+ class << self
32
+ def warn(message:, **options, &block)
33
+ log(:warn, message, options, &block)
34
+ end
35
+
36
+ def error(message:, **options, &block)
37
+ log(:error, message, options, &block)
38
+ end
39
+
40
+ def critical(message:, **options, &block)
41
+ log(:critical, message, options, &block)
42
+ end
43
+
44
+ def info(message:, **options, &block)
45
+ log(:info, message, options, &block)
46
+ end
47
+
48
+ def debug(object:, **options, &block)
49
+ log(:debug, object.inspect, options, &block)
50
+ end
51
+
52
+ def print_error(message)
53
+ color_puts(message, color: :red)
54
+ end
55
+
56
+ def print_info(message)
57
+ color_puts(message, color: :gray)
58
+ end
59
+
60
+ def print_warn(message)
61
+ color_puts(message, color: :yellow)
62
+ end
63
+
64
+ def print_debug(message)
65
+ color_puts(message)
66
+ end
67
+
68
+ def print_critical(message)
69
+ color_puts(message, color: :red, style: :blink)
70
+ end
71
+
72
+ def log(severity, message, options = {}, &block)
73
+ time = Time.now.strftime('%Y-%m-%d %H:%M:%S')
74
+ if options.any?
75
+ message = options.map { |k, v| "[#{k}: #{v}]" }.join << " #{message}"
76
+ end
77
+ send("print_#{severity}", "[#{time}] #{severity.upcase} #{message}")
78
+
79
+ block&.call(message, options) if block_given?
80
+ end
81
+ end
82
+ end
83
+ end
@@ -12,7 +12,7 @@ module Rubee
12
12
 
13
13
  module ClassMethods
14
14
  def pluralize_class_name
15
- name.pluralize.downcase
15
+ name.pluralize.downcase.snakeize
16
16
  end
17
17
 
18
18
  def accessor_names
@@ -2,6 +2,7 @@ module Rubee
2
2
  class SequelObject
3
3
  include Rubee::DatabaseObjectable
4
4
  using ChargedString
5
+ using ChargedHash
5
6
 
6
7
  def destroy(cascade: false, **_options)
7
8
  if cascade
@@ -23,7 +24,7 @@ module Rubee
23
24
  args = to_h.dup&.transform_keys(&:to_sym)
24
25
  if args[:id]
25
26
  begin
26
- udpate(args)
27
+ update(args)
27
28
  rescue StandardError => _e
28
29
  return false
29
30
  end
@@ -107,7 +108,6 @@ module Rubee
107
108
  # > comment.user
108
109
  # > <user>
109
110
  def owns_one(assoc, options = {})
110
- Sequel::Model.one_to_one(assoc, **options)
111
111
  fk_name ||= "#{name.to_s.downcase}_id"
112
112
  define_method(assoc) do
113
113
  Object.const_get(assoc.capitalize).where(fk_name.to_sym => id)&.first
@@ -180,7 +180,7 @@ module Rubee
180
180
  def serialize(suquel_dataset, klass = nil)
181
181
  klass ||= self
182
182
  suquel_dataset.map do |record_hash|
183
- target_klass_fields = DB[klass.name.pluralize.downcase.to_sym].columns
183
+ target_klass_fields = DB[klass.name.pluralize.downcase.camelize.to_sym].columns
184
184
  klass_attributes = record_hash.filter { target_klass_fields.include?(_1) }
185
185
  klass.new(**klass_attributes)
186
186
  end
@@ -0,0 +1,40 @@
1
+ module Rubee
2
+ class Router
3
+ include Singleton
4
+
5
+ HTTP_METHODS = %i[get post put patch delete head connect options trace].freeze unless defined?(HTTP_METHODS)
6
+
7
+ attr_reader :request, :routes
8
+
9
+ @routes = []
10
+
11
+ class << self
12
+ def draw
13
+ yield(self) if block_given?
14
+ end
15
+
16
+ def route_for(request)
17
+ method = (request.params['_method'] || request.request_method).downcase.to_sym
18
+ @routes.find do |route|
19
+ return route if request.path == route[:path] && request.request_method&.downcase&.to_sym == route[:method]
20
+
21
+ pattern = route[:path].gsub(/{.*?}/, '([^/]+)')
22
+ regex = /^#{pattern}$/
23
+ regex.match?(request.path) && method.to_s == route[:method].to_s
24
+ end
25
+ end
26
+
27
+ def set_route(path, to:, method: __method__, **args)
28
+ controller, action = to.split('#')
29
+ @routes.delete_if { |route| route[:path] == path && route[:method] == method }
30
+ @routes << { path:, controller:, action:, method:, **args }
31
+ end
32
+
33
+ HTTP_METHODS.each do |method|
34
+ define_method method do |path, to:, **args|
35
+ set_route(path, to:, method: method, **args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end