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.
- checksums.yaml +4 -4
- data/bin/rubee +6 -295
- data/lib/app/views/index.html +1 -1
- data/lib/config/base_configuration.rb +25 -7
- data/lib/db/create_addresses.rb +17 -0
- data/lib/inits/charged_hash.rb +16 -0
- data/lib/inits/charged_string.rb +12 -0
- data/lib/js/app.js +3 -3
- data/lib/package.json +1 -1
- data/lib/rubee/async/fiber_queue.rb +27 -0
- data/lib/rubee/async/thread_async.rb +1 -1
- data/lib/rubee/async/thread_pool.rb +32 -34
- data/lib/rubee/autoload.rb +86 -0
- data/lib/rubee/cli/attach.rb +124 -0
- data/lib/rubee/cli/command.rb +41 -0
- data/lib/rubee/cli/console.rb +39 -0
- data/lib/rubee/cli/db.rb +105 -0
- data/lib/rubee/cli/generate.rb +33 -0
- data/lib/rubee/cli/project.rb +124 -0
- data/lib/rubee/cli/react.rb +28 -0
- data/lib/rubee/cli/routes.rb +18 -0
- data/lib/rubee/cli/server.rb +52 -0
- data/lib/rubee/cli/test.rb +24 -0
- data/lib/rubee/cli/version.rb +15 -0
- data/lib/rubee/configuration.rb +83 -0
- data/lib/rubee/controllers/base_controller.rb +12 -6
- data/lib/rubee/controllers/extensions/auth_tokenable.rb +2 -2
- data/lib/rubee/extensions/hookable.rb +9 -2
- data/lib/rubee/generator.rb +160 -0
- data/lib/rubee/logger.rb +83 -0
- data/lib/rubee/models/database_objectable.rb +1 -1
- data/lib/rubee/models/sequel_object.rb +3 -3
- data/lib/rubee/router.rb +40 -0
- data/lib/rubee.rb +13 -317
- data/lib/tests/async/thread_async_test.rb +9 -5
- data/lib/tests/cli/attach_test.rb +36 -0
- data/lib/tests/{auth_tokenable_test.rb → controllers/auth_tokenable_test.rb} +2 -2
- data/lib/tests/controllers/base_controller_test.rb +23 -0
- data/lib/tests/controllers/hookable_test.rb +220 -0
- data/lib/tests/{rubeeapp_test.rb → controllers/rubeeapp_test.rb} +3 -2
- data/lib/tests/example_models/address.rb +5 -0
- data/lib/tests/example_models/user.rb +1 -0
- data/lib/tests/logger_test.rb +76 -0
- data/lib/tests/{account_model_test.rb → models/account_model_test.rb} +1 -1
- data/lib/tests/{comment_model_test.rb → models/comment_model_test.rb} +13 -1
- data/lib/tests/models/db_objectable_test.rb +21 -0
- data/lib/tests/models/seralizable_test.rb +36 -0
- data/lib/tests/{user_model_test.rb → models/user_model_test.rb} +32 -1
- data/lib/tests/rubee_attach_test.rb +0 -0
- data/lib/tests/test.db +0 -0
- data/lib/tests/test_helper.rb +20 -2
- data/readme.md +174 -15
- metadata +34 -9
- data/lib/app/views/apples_.erb +0 -1
- data/lib/app/views/s_.erb +0 -1
- /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
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
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
|
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
|
-
|
42
|
+
result = nil
|
43
|
+
handler.call do
|
44
|
+
result = super(*args, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
result
|
43
48
|
else
|
44
|
-
send(handler)
|
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
|
data/lib/rubee/logger.rb
ADDED
@@ -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
|
@@ -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
|
-
|
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
|
data/lib/rubee/router.rb
ADDED
@@ -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
|