ru.Bee 1.4.0 → 1.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f60bb8f17e35447c9cedd635d719a6b0d988486f9fe04668e0e07763a99cb740
4
- data.tar.gz: a56dcac76060f5bb9ec71959ef4d21fa7c3e9b858eb6088e873e3ba50e4684f0
3
+ metadata.gz: 209e21bbf6b7d2a4986fd92968f33b4c53b3f9699577eadcd2e35674f7767e3c
4
+ data.tar.gz: 9a9d73b4b2822a0e423371cf7cef7596aac3e7ee9574d0e722e7885b42959a4b
5
5
  SHA512:
6
- metadata.gz: 03be1679bf032a1f292b4490a41286eb02f3a56984cf8dc7281715db53f880dc3ded8feb445ae41a22d60c2a4c3a5e47f8b8fe1be5f4c8188d26e57d403b746e
7
- data.tar.gz: 0ffa4e0cc8c436d2ce7ac784c92465333d12ebaa67172f08bcfc55e59dd6f29472aa70211a3c2b766c234ef089b51d1c47a130cc6c0c8ab35e896f659b1ba43d
6
+ metadata.gz: e32baee79be70d8259d1c6643d35ccb6ef31cadad1c53d5a085fd15eeea521a394a97b72044b68f7ef62ab9e8bb90b14356a9e38f219797b404f2a02d061a676
7
+ data.tar.gz: a9b7e464b0e4bf11f262a70cd593c0aa65f0eaf9a8e836ece33256df533feab5f341d0db3077d64c234535bb7f8635b1e034735003044d37ae8f40740d8ea54e
data/bin/rubee CHANGED
@@ -5,7 +5,6 @@ require 'fileutils'
5
5
  require_relative '../lib/inits/print_colors'
6
6
  require_relative '../lib/rubee'
7
7
 
8
-
9
8
  ENV['RACK_ENV'] ||= 'development'
10
9
 
11
10
  LIB_ROOT = File.expand_path('../lib', File.dirname(__FILE__))
@@ -165,7 +164,7 @@ elsif command == 'project'
165
164
  color_puts "Project #{project_name} created successfully at #{target_dir}", color: :green
166
165
 
167
166
  elsif command == 'version'
168
- color_puts "ru.Bee v#{Rubee::VERSION}", color: :yellow
167
+ color_puts "ruBee v#{Rubee::VERSION}", color: :yellow
169
168
  elsif command == 'routes'
170
169
  file = Rubee::PROJECT_NAME == 'rubee' ? File.join('/lib', 'config/routes.rb') : 'config/routes.rb'
171
170
  routes = eval(File.read(file))
@@ -190,7 +189,6 @@ elsif %w[generate gen].include?(command)
190
189
  routes = eval(File.read(file))
191
190
  route = routes.find { |route| route[:path] == path.to_s && route[:method] == method.to_sym }
192
191
  color_puts("Route not found with path: #{path} and method: #{method}", color: :red) unless route
193
-
194
192
  Rubee::Generator.new(
195
193
  route[:model]&.[](:name),
196
194
  route[:model]&.[](:attributes),
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
3
+ import "./../../css/app.css";
3
4
 
4
5
  const Home = () => (
5
6
  <div>
@@ -0,0 +1 @@
1
+ <h1>apples_ View</h1>
@@ -4,44 +4,9 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>React App</title>
7
- <style>
8
- body {
9
- display: flex;
10
- justify-content: center;
11
- align-items: center;
12
- height: 100vh;
13
- margin: 0;
14
- background-color: #fdf6a5;
15
- text-align: center;
16
- font-family: Arial, sans-serif;
17
- }
18
- .container {
19
- padding: 20px;
20
- background: white;
21
- border-radius: 10px;
22
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
23
- }
24
- img {
25
- width: 50px;
26
- height: auto;
27
- margin-top: 10px;
28
- }
29
- h1 {
30
- font-size: 1.8rem;
31
- margin: 10px 0;
32
- }
33
- @media (max-width: 600px) {
34
- h1 {
35
- font-size: 1.5rem;
36
- }
37
- img {
38
- width: 60px;
39
- }
40
- }
41
- </style>
42
7
  </head>
43
8
  <body>
44
9
  <div id="app"></div>
45
- <script type="module" src="./../../js/bundle.js"></script>
10
+ <script type="module" src="/js/bundle.js"></script>
46
11
  </body>
47
12
  </html>
@@ -4,41 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title><%= @title || '' %></title>
7
- <style>
8
- body {
9
- display: flex;
10
- justify-content: center;
11
- align-items: center;
12
- height: 100vh;
13
- margin: 0;
14
- background-color: #fdf6a5;
15
- text-align: center;
16
- font-family: Arial, sans-serif;
17
- }
18
- .container {
19
- padding: 20px;
20
- background: white;
21
- border-radius: 10px;
22
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
23
- }
24
- img {
25
- width: 50px;
26
- height: auto;
27
- margin-top: 10px;
28
- }
29
- h1 {
30
- font-size: 1.8rem;
31
- margin: 10px 0;
32
- }
33
- @media (max-width: 600px) {
34
- h1 {
35
- font-size: 1.5rem;
36
- }
37
- img {
38
- width: 60px;
39
- }
40
- }
41
- </style>
7
+ <link rel="stylesheet" href="/css/app.css">
42
8
  </head>
43
9
  <body>
44
10
  <%= _yield_template %>
@@ -0,0 +1 @@
1
+ <h1>s_ View</h1>
data/lib/css/app.css ADDED
@@ -0,0 +1,33 @@
1
+ body {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+ height: 100vh;
6
+ margin: 0;
7
+ background-color: #fdf6a5;
8
+ text-align: center;
9
+ font-family: Arial, sans-serif;
10
+ }
11
+ .container {
12
+ padding: 20px;
13
+ background: white;
14
+ border-radius: 10px;
15
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
16
+ }
17
+ img {
18
+ width: 50px;
19
+ height: auto;
20
+ margin-top: 10px;
21
+ }
22
+ h1 {
23
+ font-size: 1.8rem;
24
+ margin: 10px 0;
25
+ }
26
+ @media (max-width: 600px) {
27
+ h1 {
28
+ font-size: 1.5rem;
29
+ }
30
+ img {
31
+ width: 60px;
32
+ }
33
+ }
@@ -1,5 +1,6 @@
1
1
  // esbuild.config.js (CommonJS style)
2
2
  const esbuild = require("esbuild");
3
+ const inlineCss = require("esbuild-plugin-inline-css");
3
4
 
4
5
  const buildOptions = {
5
6
  entryPoints: ["js/app.js"], // Can be .ts or .tsx too
@@ -11,7 +12,9 @@ const buildOptions = {
11
12
  ".jsx": "jsx",
12
13
  ".ts": "ts",
13
14
  ".tsx": "tsx",
15
+ ".css": "css",
14
16
  },
17
+ plugins: [inlineCss()],
15
18
  allowOverwrite: true,
16
19
  sourcemap: true,
17
20
  };
@@ -0,0 +1,35 @@
1
+ module ChargedString
2
+ refine String do
3
+ def pluralize
4
+ if self.end_with?('y') && !%w[a e i o u].include?(self[-2])
5
+ "#{self[0..-2]}ies" # Replace "y" with "ies"
6
+ elsif self.end_with?('s', 'x', 'z', 'ch', 'sh')
7
+ "#{self}es" # Add "es" for certain endings
8
+ else
9
+ "#{self}s" # Default to adding "s"
10
+ end
11
+ end
12
+
13
+ def plural?
14
+ return true if self.end_with?('s') && !self.end_with?('ss')
15
+
16
+ false
17
+ end
18
+
19
+ def singular?
20
+ !plural?
21
+ end
22
+
23
+ def singularize
24
+ if self.end_with?('ies') && self.length > 3
25
+ "#{self[0..-4]}y" # Convert "ies" to "y"
26
+ elsif self.end_with?('es') && %w[s x z ch sh].any? { |ending| self[-(ending.length + 2)..-3] == ending }
27
+ self[0..-3] # Remove "es" for selfs like "foxes", "buses"
28
+ elsif self.end_with?('s') && self.length > 1
29
+ self[0..-2] # Remove "s" for regular plurals
30
+ else
31
+ self # Return as-is if no plural form is detected
32
+ end
33
+ end
34
+ end
35
+ end
@@ -4,12 +4,14 @@
4
4
  def color_puts(text, color: :nil, background: :nil, style: :normal)
5
5
  colors = {
6
6
  black: 30, red: 31, green: 32, yellow: 33,
7
- blue: 34, magenta: 35, cyan: 36, white: 37
7
+ blue: 34, magenta: 35, cyan: 36, white: 37,
8
+ gray: 90
8
9
  }
9
10
 
10
11
  backgrounds = {
11
12
  black: 40, red: 41, green: 42, yellow: 43,
12
- blue: 44, magenta: 45, cyan: 46, white: 47
13
+ blue: 44, magenta: 45, cyan: 46, white: 47,
14
+ gray: 100
13
15
  }
14
16
 
15
17
  styles = {
data/lib/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "scripts": {
3
3
  "build": "node esbuild.config.js",
4
4
  "watch": "node esbuild.config.js --watch",
5
- "prepare": "npm install react react-dom react-router-dom"
5
+ "prepare": "npm install react react-dom react-router-dom esbuild-plugin-inline-css"
6
6
  },
7
7
  "dependencies": {
8
8
  "react": "^18.3.1",
@@ -28,6 +28,16 @@ module Rubee
28
28
  end
29
29
  end
30
30
 
31
+ def css
32
+ css_path = File.join(CSS_DIR, @request.path.sub('/css/', ''))
33
+
34
+ if File.exist?(css_path) && File.file?(css_path)
35
+ response_with(object: File.read(css_path), type: :css)
36
+ else
37
+ response_with(object: 'Css file is not found', type: :text)
38
+ end
39
+ end
40
+
31
41
  def response_with type: nil, object: nil, status: 200, mime_type: nil, render_view: nil, headers: {}, to: nil,
32
42
  file: nil, filename: nil, **options
33
43
  case type&.to_sym
@@ -38,6 +48,8 @@ module Rubee
38
48
  [status, headers.merge('content-type' => mime_type), [object]]
39
49
  in :js
40
50
  [status, headers.merge('content-type' => 'application/javascript'), [object]]
51
+ in :css
52
+ [status, headers.merge('content-type' => 'text/css'), [object]]
41
53
  in :file
42
54
  [
43
55
  status,
@@ -1,3 +1,5 @@
1
+ using ChargedString
2
+
1
3
  module Rubee
2
4
  module DatabaseObjectable
3
5
  def self.included(base)
@@ -11,29 +13,7 @@ module Rubee
11
13
 
12
14
  module ClassMethods
13
15
  def pluralize_class_name
14
- pluralize(name.downcase)
15
- end
16
-
17
- def pluralize(word)
18
- if word.end_with?('y') && !%w[a e i o u].include?(word[-2])
19
- "#{word[0..-2]}ies" # Replace "y" with "ies"
20
- elsif word.end_with?('s', 'x', 'z', 'ch', 'sh')
21
- "#{word}es" # Add "es" for certain endings
22
- else
23
- "#{word}s" # Default to adding "s"
24
- end
25
- end
26
-
27
- def singularize(word)
28
- if word.end_with?('ies') && word.length > 3
29
- "#{word[0..-4]}y" # Convert "ies" to "y"
30
- elsif word.end_with?('es') && %w[s x z ch sh].any? { |ending| word[-(ending.length + 2)..-3] == ending }
31
- word[0..-3] # Remove "es" for words like "foxes", "buses"
32
- elsif word.end_with?('s') && word.length > 1
33
- word[0..-2] # Remove "s" for regular plurals
34
- else
35
- word # Return as-is if no plural form is detected
36
- end
16
+ name.pluralize.downcase
37
17
  end
38
18
 
39
19
  def accessor_names
@@ -1,3 +1,5 @@
1
+ using ChargedString
2
+
1
3
  module Rubee
2
4
  class SequelObject
3
5
  include Rubee::DatabaseObjectable
@@ -11,7 +13,7 @@ module Rubee
11
13
  # destroy related records
12
14
  tables_with_fk.each do |table|
13
15
  fk_name ||= "#{self.class.name.to_s.downcase}_id".to_sym
14
- target_klass = Object.const_get(self.class.singularize(table.to_s).capitalize)
16
+ target_klass = Object.const_get(table.to_s.singularize.capitalize)
15
17
  target_klass.where(fk_name => id).map(&:destroy)
16
18
  end
17
19
  end
@@ -81,7 +83,7 @@ module Rubee
81
83
  # > user.comments
82
84
  # > [<comment1>, <comment2>]
83
85
  def owns_many(assoc, fk_name: nil, over: nil, **_options)
84
- singularized_assoc_name = singularize(assoc.to_s)
86
+ singularized_assoc_name = assoc.to_s.singularize
85
87
  fk_name ||= "#{name.to_s.downcase}_id"
86
88
 
87
89
  define_method(assoc) do
@@ -175,7 +177,7 @@ module Rubee
175
177
  def serialize(suquel_dataset, klass = nil)
176
178
  klass ||= self
177
179
  suquel_dataset.map do |record_hash|
178
- target_klass_fields = DB[pluralize(klass.name.downcase).to_sym].columns
180
+ target_klass_fields = DB[klass.name.pluralize.downcase.to_sym].columns
179
181
  klass_attributes = record_hash.filter { target_klass_fields.include?(_1) }
180
182
  klass.new(**klass_attributes)
181
183
  end
data/lib/rubee.rb CHANGED
@@ -14,7 +14,8 @@ module Rubee
14
14
  LIB = PROJECT_NAME == 'rubee' ? 'lib/' : '' unless defined?(LIB)
15
15
  IMAGE_DIR = File.join(APP_ROOT, LIB, 'images') unless defined?(IMAGE_DIR)
16
16
  JS_DIR = File.join(APP_ROOT, LIB, 'js') unless defined?(JS_DIR)
17
- VERSION = '1.4.0'
17
+ CSS_DIR = File.join(APP_ROOT, LIB, 'css') unless defined?(CSS_DIR)
18
+ VERSION = '1.5.1'
18
19
 
19
20
  class Application
20
21
  include Singleton
@@ -34,7 +35,7 @@ module Rubee
34
35
  route = Router.route_for(request)
35
36
  # if react is the view so we would like to delegate not cauth by rubee routes to it.
36
37
  if Rubee::Configuration.react[:on] && !route
37
- index = File.read File.join(Rubee::APP_ROOT, Rubee::LIB, 'app/views', 'index.html')
38
+ index = File.read(File.join(Rubee::APP_ROOT, Rubee::LIB, 'app/views', 'index.html'))
38
39
  return [200, { 'content-type' => 'text/html' }, [index]]
39
40
  end
40
41
  # if not found return 404
@@ -213,20 +214,23 @@ module Rubee
213
214
  end
214
215
 
215
216
  class Generator
216
- def initialize(model_name, attributes, controller_name, action_name, **options)
217
+ require_relative 'inits/charged_string'
218
+ using ChargedString
219
+ def initialize(model_name, model_attributes, controller_name, action_name, **options)
217
220
  @model_name = model_name&.downcase
218
- @attributes = attributes
219
- @plural_name = controller_name.to_s.gsub('Controller', '').downcase.to_s
221
+ @model_attributes = model_attributes || []
222
+ @base_name = controller_name.to_s.gsub('Controller', '').downcase.to_s
223
+ color_puts("base_name: #{@base_name}", color: :gray)
224
+ @plural_name = @base_name.plural? ? @base_name : @base_name.pluralize
220
225
  @action_name = action_name
221
- @controller_name = controller_name
222
226
  @react = options[:react] || {}
223
227
  end
224
228
 
225
229
  def call
226
230
  generate_model if @model_name
227
231
  generate_db_file if @model_name
228
- generate_controller if @controller_name && @action_name
229
- generate_view if @controller_name
232
+ generate_controller if @base_name && @action_name
233
+ generate_view if @base_name
230
234
  end
231
235
 
232
236
  private
@@ -240,7 +244,7 @@ module Rubee
240
244
 
241
245
  content = <<~RUBY
242
246
  class #{@model_name.capitalize} < Rubee::SequelObject
243
- attr_accessor #{@attributes.map { |hash| ":#{hash[:name]}" }.join(', ')}
247
+ #{'attr_accessor ' + @model_attributes.map { |hash| ":#{hash[:name]}" }.join(', ') unless @model_attributes.empty?}
244
248
  end
245
249
  RUBY
246
250
 
@@ -249,14 +253,14 @@ module Rubee
249
253
  end
250
254
 
251
255
  def generate_controller
252
- controller_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "app/controllers/#{@plural_name}_controller.rb")
256
+ controller_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "app/controllers/#{@base_name}_controller.rb")
253
257
  if File.exist?(controller_file)
254
- puts "Controller #{@plural_name} already exists. Remove it if you want to regenerate"
258
+ puts "Controller #{@base_name} already exists. Remove it if you want to regenerate"
255
259
  return
256
260
  end
257
261
 
258
262
  content = <<~RUBY
259
- class #{@plural_name.capitalize}Controller < Rubee::BaseController
263
+ class #{@base_name.capitalize}Controller < Rubee::BaseController
260
264
  def #{@action_name}
261
265
  response_with
262
266
  end
@@ -264,24 +268,24 @@ module Rubee
264
268
  RUBY
265
269
 
266
270
  File.open(controller_file, 'w') { |file| file.write(content) }
267
- color_puts("Controller #{@plural_name} created", color: :green)
271
+ color_puts("Controller #{@base_name} created", color: :green)
268
272
  end
269
273
 
270
274
  def generate_view
271
275
  if @react[:view_name]
272
276
  view_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "app/views/#{@react[:view_name]}")
273
277
  content = <<~JS
274
- import React, { useEffect, useState } from "react";
275
- // 1. Add your logic that fetches data
276
- // 2. Do not forget to add respective react route
277
- export function User() {
278
-
279
- return (
280
- <div>
281
- <h2>#{@react[:view_name]} view</h2>
282
- </div>
283
- );
284
- }
278
+ import React, { useEffect, useState } from "react";
279
+ // 1. Add your logic that fetches data
280
+ // 2. Do not forget to add respective react route
281
+ export function #{@react[:view_name].gsub(/\.(.*)+$/, '').capitalize}() {
282
+
283
+ return (
284
+ <div>
285
+ <h2>#{@react[:view_name]} view</h2>
286
+ </div>
287
+ );
288
+ }
285
289
  JS
286
290
  else
287
291
  view_file = File.join(Rubee::APP_ROOT, Rubee::LIB, "app/views/#{@plural_name}_#{@action_name}.erb")
@@ -311,6 +315,11 @@ module Rubee
311
315
  content = <<~RUBY
312
316
  class Create#{@plural_name.capitalize}
313
317
  def call
318
+ return if Rubee::SequelObject::DB.tables.include?(:#{@plural_name})
319
+
320
+ Rubee::SequelObject::DB.create_table(:#{@plural_name}) do
321
+ #{@model_attributes.map { |attribute| generate_sequel_schema(attribute) }.join("\n\t\t\t")}
322
+ end
314
323
  end
315
324
  end
316
325
  RUBY
@@ -318,5 +327,40 @@ module Rubee
318
327
  File.open(db_file, 'w') { |file| file.write(content) }
319
328
  color_puts("DB file for #{@plural_name} created", color: :green)
320
329
  end
330
+
331
+ def generate_sequel_schema(attribute)
332
+ type = attribute[:type]
333
+ name = if attribute[:name].is_a?(Array)
334
+ attribute[:name].map { |nom| ":#{nom}" }.join(", ").prepend('[') + ']'
335
+ else
336
+ ":#{attribute[:name]}"
337
+ end
338
+ table = attribute[:table] || 'replace_with_table_name'
339
+ options = attribute[:options] || {}
340
+
341
+ lookup_hash = {
342
+ primary: "primary_key #{name}",
343
+ string: "String #{name}",
344
+ text: "String #{name}, text: true",
345
+ integer: "Integer #{name}",
346
+ date: "Date #{name}",
347
+ datetime: "DateTime #{name}",
348
+ time: "Time #{name}",
349
+ boolean: "TrueClass #{name}",
350
+ bigint: "Bignum #{name}",
351
+ decimal: "BigDecimal #{name}",
352
+ foreign_key: "foreign_key #{name}, :#{table}",
353
+ index: "index #{name}",
354
+ unique: "unique #",
355
+ }
356
+
357
+ statement = lookup_hash[type.to_sym]
358
+
359
+ options.keys.each do |key|
360
+ statement += ", #{key}: '#{options[key]}'"
361
+ end
362
+
363
+ statement
364
+ end
321
365
  end
322
366
  end
@@ -0,0 +1,190 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe 'Rubee::Generator' do
4
+ describe 'generates Sequel schema lines' do
5
+ after do
6
+ File.delete('lib/app/models/apple.rb') if File.exist?('lib/app/models/apple.rb')
7
+ File.delete('lib/db/create_apples.rb') if File.exist?('lib/db/create_apples.rb')
8
+ end
9
+ it 'for string with just name' do
10
+ generator = Rubee::Generator.new(nil, nil, nil, nil)
11
+
12
+ attribute = { name: 'something', type: :string }
13
+
14
+ text = generator.send(:generate_sequel_schema, attribute)
15
+
16
+ _(text).must_equal('String :something')
17
+ end
18
+
19
+ it 'for string name and options' do
20
+ generator = Rubee::Generator.new(nil, nil, nil, nil)
21
+
22
+ attribute = { name: 'something', type: :string, options: { curse: 'squirrel' } }
23
+
24
+ text = generator.send(:generate_sequel_schema, attribute)
25
+
26
+ _(text).must_equal("String :something, curse: 'squirrel'")
27
+ end
28
+ end
29
+
30
+ describe 'generates Sequel file' do
31
+ after do
32
+ File.delete('lib/app/models/apple.rb') if File.exist?('lib/app/models/apple.rb')
33
+ File.delete('lib/db/create_apples.rb') if File.exist?('lib/db/create_apples.rb')
34
+ end
35
+
36
+ it 'not without a model' do
37
+ generator = Rubee::Generator.new(nil, nil, nil, nil)
38
+ generator.call
39
+
40
+ _(File.exist?('lib/db/create_apples.rb')).must_equal false
41
+ end
42
+
43
+ it 'with a model only' do
44
+ generator = Rubee::Generator.new('apple', nil, 'apples', nil)
45
+ generator.call
46
+
47
+ _(File.exist?('lib/db/create_apples.rb')).must_equal true
48
+
49
+ lines = File.readlines('lib/db/create_apples.rb').map(&:chomp).join("\n")
50
+
51
+ _(lines.include?('class CreateApples')).must_equal true
52
+ _(lines.include?('def call')).must_equal true
53
+ _(lines.include?('return if Rubee::SequelObject::DB.tables.include?(:apples)')).must_equal true
54
+ _(lines.include?('Rubee::SequelObject::DB.create_table(:apples) do')).must_equal true
55
+ _(lines.include?('String')).must_equal false
56
+ _(lines.include?('end')).must_equal true
57
+ end
58
+
59
+ it 'with a model with attributes' do
60
+ generator = Rubee::Generator.new('apple', [{ name: 'title', type: :string }, {name: 'content', type: :text }], 'apples', nil)
61
+ generator.call
62
+
63
+ _(File.exist?('lib/db/create_apples.rb')).must_equal true
64
+
65
+ lines = File.readlines('lib/db/create_apples.rb').map(&:chomp).join("\n")
66
+
67
+ _(lines.include?('class CreateApples')).must_equal true
68
+ _(lines.include?('def call')).must_equal true
69
+ _(lines.include?('return if Rubee::SequelObject::DB.tables.include?(:apples)')).must_equal true
70
+ _(lines.include?('Rubee::SequelObject::DB.create_table(:apples) do')).must_equal true
71
+ _(lines.include?('String :title')).must_equal true
72
+ _(lines.include?('String :content, text: true')).must_equal true
73
+ _(lines.include?('end')).must_equal true
74
+ end
75
+
76
+ it 'with a model with different attributes' do
77
+ generator = Rubee::Generator.new('apple', [{name: 'id', type: :bigint}, {name: 'colour', type: :string }, {name: 'weight', type: :integer }], 'apples', nil)
78
+ generator.call
79
+
80
+ _(File.exist?('lib/db/create_apples.rb')).must_equal true
81
+
82
+ lines = File.readlines('lib/db/create_apples.rb').map(&:chomp).join("\n")
83
+
84
+ _(lines.include?('class CreateApples')).must_equal true
85
+ _(lines.include?('def call')).must_equal true
86
+ _(lines.include?('return if Rubee::SequelObject::DB.tables.include?(:apples)')).must_equal true
87
+ _(lines.include?('Rubee::SequelObject::DB.create_table(:apples) do')).must_equal true
88
+ _(lines.include?('Bignum :id')).must_equal true
89
+ _(lines.include?('String :colour')).must_equal true
90
+ _(lines.include?('Integer :weight')).must_equal true
91
+ _(lines.include?('end')).must_equal true
92
+ end
93
+
94
+ it 'with a model with an attribute with multiple names' do
95
+ generator = Rubee::Generator.new('apple', [{ name: ['blue_id', 'shoe_id'], type: :foreign_key, table: 'blue_and_shoe_join_tb' }], 'apples', nil)
96
+ generator.call
97
+
98
+ _(File.exist?('lib/db/create_apples.rb')).must_equal true
99
+
100
+ lines = File.readlines('lib/db/create_apples.rb').map(&:chomp).join("\n")
101
+
102
+ _(lines.include?('class CreateApples')).must_equal true
103
+ _(lines.include?('def call')).must_equal true
104
+ _(lines.include?('return if Rubee::SequelObject::DB.tables.include?(:apples)')).must_equal true
105
+ _(lines.include?('Rubee::SequelObject::DB.create_table(:apples) do')).must_equal true
106
+ _(lines.include?('foreign_key [:blue_id, :shoe_id]')).must_equal true
107
+ _(lines.include?(':blue_and_shoe_join_tb')).must_equal true
108
+ _(lines.include?('end')).must_equal true
109
+ end
110
+
111
+ it 'with a model with a foreign_key without table' do
112
+ generator = Rubee::Generator.new('apple', [{ name: 'blue_id', type: :foreign_key }], 'apples', nil)
113
+ generator.call
114
+
115
+ _(File.exist?('lib/db/create_apples.rb')).must_equal true
116
+
117
+ lines = File.readlines('lib/db/create_apples.rb').map(&:chomp).join("\n")
118
+
119
+ _(lines.include?('class CreateApples')).must_equal true
120
+ _(lines.include?('def call')).must_equal true
121
+ _(lines.include?('return if Rubee::SequelObject::DB.tables.include?(:apples)')).must_equal true
122
+ _(lines.include?('Rubee::SequelObject::DB.create_table(:apples) do')).must_equal true
123
+ _(lines.include?('foreign_key :blue_id')).must_equal true
124
+ _(lines.include?(':replace_with_table_name')).must_equal true
125
+ _(lines.include?('end')).must_equal true
126
+ end
127
+ end
128
+
129
+ describe 'generates Model file' do
130
+ after do
131
+ File.delete('lib/app/models/apple.rb') if File.exist?('lib/app/models/apple.rb')
132
+ File.delete('lib/db/create_apples.rb') if File.exist?('lib/db/create_apples.rb')
133
+ end
134
+
135
+ it 'not without a model' do
136
+ generator = Rubee::Generator.new(nil, nil, nil, nil)
137
+ generator.call
138
+
139
+ _(File.exist?('lib/app/models/apple.rb')).must_equal false
140
+ end
141
+
142
+ it 'with a model only' do
143
+ generator = Rubee::Generator.new('apple', nil, 'apples', nil)
144
+ generator.call
145
+
146
+ _(File.exist?('lib/app/models/apple.rb')).must_equal true
147
+
148
+ lines = File.readlines('lib/app/models/apple.rb').map(&:chomp).join("\n")
149
+
150
+ _(lines.include?('class Apple < Rubee::SequelObject')).must_equal true
151
+ _(lines.include?('attr_accessor')).must_equal false
152
+ _(lines.include?('end')).must_equal true
153
+ end
154
+
155
+ it 'with a model with attributes' do
156
+ generator = Rubee::Generator.new('apple', [{ name: 'title', type: :string }, {name: 'content', type: :text }], 'apples', nil)
157
+ generator.call
158
+
159
+ _(File.exist?('lib/app/models/apple.rb')).must_equal true
160
+
161
+ lines = File.readlines('lib/app/models/apple.rb').map(&:chomp).join("\n")
162
+
163
+ _(lines.include?('class Apple < Rubee::SequelObject')).must_equal true
164
+ _(lines.include?('attr_accessor')).must_equal true
165
+ _(lines.include?(':title, :content')).must_equal true
166
+ _(lines.include?('end')).must_equal true
167
+ end
168
+
169
+ it 'with a model with different attributes' do
170
+ generator = Rubee::Generator.new('apple', [{name: 'id', type: :bigInt}, {name: 'colour', type: :string }, {name: 'weight', type: :integer }], 'apples', nil)
171
+ generator.call
172
+
173
+ _(File.exist?('lib/app/models/apple.rb')).must_equal true
174
+
175
+ lines = File.readlines('lib/app/models/apple.rb').map(&:chomp).join("\n")
176
+
177
+ _(lines.include?('class Apple < Rubee::SequelObject')).must_equal true
178
+ _(lines.include?('attr_accessor')).must_equal true
179
+ _(lines.include?(':id, :colour, :weight')).must_equal true
180
+ _(lines.include?('end')).must_equal true
181
+ end
182
+ end
183
+
184
+ describe 'generates View file' do
185
+
186
+ end
187
+
188
+ describe 'generates Controller file' do
189
+ end
190
+ end
@@ -7,14 +7,6 @@ class RubeeAppTest < Minitest::Test
7
7
  Rubee::Application.instance
8
8
  end
9
9
 
10
- def setup
11
- Rubee::Configuration.setup(env = :test) { _1.react = { on: false, env: } }
12
- end
13
-
14
- def teardown
15
- Rubee::Configuration.setup(env = :test) { _1.react = { on: false, env: } }
16
- end
17
-
18
10
  def test_welcome_route
19
11
  get('/')
20
12
 
@@ -30,10 +22,13 @@ class RubeeAppTest < Minitest::Test
30
22
 
31
23
  def test_react_home
32
24
  Rubee::Configuration.setup(env = :test) { _1.react = { on: true, env: } }
25
+
33
26
  get('/home')
34
27
 
35
28
  assert_equal(200, last_response.status)
36
29
  assert_includes(last_response.body, '<div id="app">')
37
30
  assert_includes(last_response.body, 'bundle.js')
31
+
32
+ Rubee::Configuration.setup(env = :test) { _1.react = { on: false, env: } }
38
33
  end
39
34
  end
data/lib/tests/test.db CHANGED
Binary file
@@ -153,9 +153,13 @@ describe 'User model' do
153
153
  User.destroy_all(cascade: true)
154
154
  end
155
155
 
156
+ before do
157
+ User.destroy_all(cascade: true)
158
+ end
159
+
156
160
  describe 'when there are records' do
157
161
  it 'returns all records' do
158
- skip "This is Flaky test that blcoks dev, need to be fixed"
162
+ # skip "This is Flaky test that blcoks dev, need to be fixed"
159
163
  user = User.new(email: 'ok-test@test.com', password: '123')
160
164
  user2 = User.new(email: 'ok-test2@test.com', password: '123')
161
165
  user.save
data/readme.md CHANGED
@@ -24,10 +24,9 @@ All greaet features are yet to come!
24
24
  - **Contract driven**: Define your API contracts in a simple, declarative manner. And generate the files for you.
25
25
  - **Fast**: Optimized for speed, providing a quick response to requests. Everything is relative, I know!
26
26
  - **Rack**: Rack backed. All Rack api is available for integration.
27
- - **Router**: Router driven - generates all required files from the routes.
28
27
  - **Databases**: Sqlite3, Postgres, Mysql and many more supported by sequel gem.
29
28
  - **Views**: Json, ERB and plain HTML and ..
30
- - **React** Supported out of the box as a rubee view
29
+ - **React** is supported out of the box as a rubee view
31
30
  - **Bundlable** Charge your ruBee with any gem you need and update your project with bundle.
32
31
  - **ORM** All models are natively ORM objects, however you can use it as a blueurpint for any datasources.
33
32
  - **Authentificatable** Add JWT authentification easily to any controller action.
@@ -51,8 +50,7 @@ cd my_project
51
50
  3. Install dependencies
52
51
 
53
52
  ***Prerequisites***<br />
54
- **ruBee** is using **Sqlite** as a default database. However you can pick up any other database supported by sequel gem.
55
- Aside that, make sure:
53
+ Make sure:
56
54
  **Ruby** language (3+) is installed
57
55
  **Bundler** is installed
58
56
 
@@ -67,35 +65,46 @@ rubee start
67
65
 
68
66
  5. Open your browser and go to http://localhost:7000
69
67
 
70
- ## Create API contract and generate files from the routes
71
- 1. Add the routes to the routes.rb
72
- ```bash
73
- Rubee::Router.draw do |router|
74
- ...
75
- # draw the contract
76
- router.get "/apples", to: "apples#index",
77
- model: {
78
- name: "apple",
79
- attributes: [
80
- { name: 'id', type: :integer },
81
- { name: 'colour', type: :string },
82
- { name: 'weight', type: :integer }
83
- ]
84
- }
85
- end
86
- ```
87
- 2. genrate the files
88
- ```bash
89
- rubee generate get /apples
90
- ```
91
- 3. This will generate the following files
68
+ ## Run the tests
92
69
  ```bash
93
- ./app/controllers/apples_controller.rb # Controller with respective action
94
- ./app/models/apple.rb # Model that acts as ORM
95
- ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
96
- ./db/create_items.rb # Database migration file needed for creating repsective table
70
+ rubee test
97
71
  ```
98
- 4. Fill those files with the logic you need and run the server again!
72
+
73
+ ## Create API contract and generate files from the routes
74
+ 1. Add the routes to the routes.rb
75
+ ```ruby
76
+ Rubee::Router.draw do |router|
77
+ ...
78
+ # draw the contract
79
+ router.get "/apples", to: "apples#index",
80
+ model: {
81
+ name: "apple",
82
+ attributes: [
83
+ { name: 'id', type: :primary },
84
+ { name: 'colour', type: :string },
85
+ { name: 'weight', type: :integer }
86
+ ]
87
+ }
88
+ end
89
+ ```
90
+ 2. generate the files
91
+ ```bash
92
+ rubee generate get /apples
93
+ ```
94
+ - This will generate the following files
95
+ ```bash
96
+ ./app/controllers/apples_controller.rb # Controller with respective action
97
+ ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
98
+ ./app/models/apple.rb # Model that acts as ORM
99
+ ./db/create_apples.rb # Database migration file needed for creating repsective table
100
+ ```
101
+
102
+ 3. Run the initial db migration
103
+ ```bash
104
+ rubee db run:all
105
+ ```
106
+
107
+ 5. Fill the generated files with the logic you need and run the server again!
99
108
 
100
109
  ## Model
101
110
  Model in ruBee is just simple ruby object that can be serilalized in the view
@@ -107,8 +116,8 @@ Here below is a simple example on how it can be used by rendering json from in m
107
116
  #ApplesController
108
117
 
109
118
  def show
110
- # in memory example
111
- apples = [Apple.new(colour: 'red', weight: '1lb'), Apple.new(colour: 'green', weight: '1lb')]
119
+ # In memory example
120
+ apples = [Apple.new(colour: 'red', weight: '1lb'), Apple.new(colour: 'green', weight: '1lb')]
112
121
  apple = apples.find { |apple| apple.colour = params[:colour] }
113
122
 
114
123
  response_with object: apple, type: :json
@@ -122,8 +131,8 @@ Just make sure Serializable module included in the target class.
122
131
  attr_accessor :id, :colour, :weight
123
132
  end
124
133
  ```
125
- However, you can simply turn it to ORM object by extending database class.
126
-
134
+ However, you can simply turn it to ORM object by extending database class Rubee::SequelObject.
135
+ This one is already serializable and charged with hooks.
127
136
  ```Ruby
128
137
  class Apple < Rubee::SequelObject
129
138
  attr_accessor :id, :colour, :weight
@@ -131,7 +140,6 @@ However, you can simply turn it to ORM object by extending database class.
131
140
  ```
132
141
 
133
142
  So in the controller you would need to query your target object now.
134
-
135
143
  ```ruby
136
144
  #ApplesController
137
145
 
@@ -276,10 +284,139 @@ irb(main):010> .then { |dataset| Comment.serialize(dataset) }
276
284
  This is recommended when you want to run one query and serialize it back to Rubee object only once.
277
285
  So it may safe some resources.
278
286
 
287
+ ## Routing
288
+ Rubee uses explicit routes. In the routes.rb yout can define routes for any of the main HTTP methods. You can also add any matched parameter denoted by a pair of `{ }` in the path of the route. Eg. `/path/to/{a_key}/somewhere`
289
+
290
+ ### Routing methods
291
+ ``` ruby
292
+ Rubee::Router.draw do |router|
293
+ router.get '/posts', to: 'posts#index'
294
+ router.post '/posts', to: 'posts#create'
295
+ router.patch '/posts/{id}', to: 'posts#update'
296
+ router.put '/posts/{id}', to: 'posts#update'
297
+ router.delete '/posts/{id}', to: 'posts#delete'
298
+ router.head '/posts', to: 'posts#index'
299
+ router.connect '/posts', to: 'posts#index'
300
+ router.options '/posts', to: 'posts#index'
301
+ router.trace '/posts', to: 'posts#index'
302
+ end
303
+ ```
304
+
305
+ As you see above every route is set up as:\
306
+ `route.http_method path, to: "controller#action", model { ...optional }`
307
+
308
+ ### Defining Model attributes in routes
309
+ One of Rubee's unique traits is where we can define our models for generation. You've seen above one possible way you can set up.
310
+
311
+ ```ruby
312
+ Rubee::Router.draw do |router|
313
+ ...
314
+ # draw the contract
315
+ router.get "/apples", to: "apples#index",
316
+ model: {
317
+ name: "apple",
318
+ attributes: [
319
+ { name: 'id', type: :primary },
320
+ { name: 'colour', type: :string },
321
+ { name: 'weight', type: :integer }
322
+ ]
323
+ }
324
+ end
325
+ ```
326
+
327
+ There are many other keys supported by us and Sequel to help generate your initial db files. Other supported attribute key types are:
328
+ ``` ruby
329
+ [
330
+ { name: 'key1', type: :primary},
331
+ { name: 'key2', type: :string },
332
+ { name: 'key3', type: :text },
333
+ { name: 'key4', type: :integer },
334
+ { name: 'key5', type: :date },
335
+ { name: 'key6', type: :datetime },
336
+ { name: 'key7', type: :time },
337
+ { name: 'key8', type: :boolean },
338
+ { name: 'key9', type: :bigint },
339
+ { name: 'key10', type: :decimal },
340
+ { name: 'key11', type: :foreign_key },
341
+ { name: 'key12', type: :index },
342
+ { name: 'key13', type: :unique }
343
+ ]
344
+ ```
345
+ Every attribute can have a set of options passed based on their related [Sequel schema definition](https://github.com/jeremyevans/sequel/blob/master/doc/schema_modification.rdoc).
346
+
347
+ An example of this would be for the type string: \
348
+ `{name: 'key', type: :string, options: { size: 50, fixed: true } }`
349
+
350
+ Gets translated to:\
351
+ `String :key, size: 50, fixed: true`
352
+
353
+ ### Generation from routes
354
+ As long as you have a `{ model: 'something' }` passed to your given route you can use it to generate your initial model files. If only a `path` and a `to:` are defined will only generate a controller and a corresponding view.
355
+
356
+ To generate based on a get route for the path /apples:\
357
+ `rubee generate get /apples` or `rubee gen get /apples`\
358
+
359
+ To generate base on a patch request for the path /apples/{id}:\
360
+ `rubee generate patch /apples/{id}` or `rubee gen patch /apples/{id}`
361
+
279
362
 
363
+ Example:
364
+ ```ruby
365
+ Rubee::Router.draw do |router|
366
+ ...
367
+ # draw the contract
368
+ router.get "/apples", to: "apples#index"
369
+ end
370
+ ```
371
+ Will Generate:
372
+ ```bash
373
+ ./app/controllers/apples_controller.rb # Controller with respective action
374
+ ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
375
+ ```
376
+
377
+ Example 2:
378
+ ```ruby
379
+ Rubee::Router.draw do |router|
380
+ ...
381
+ # draw the contract
382
+ router.get "/apples", to: "apples#index", model: { name: 'apple' }
383
+ end
384
+ ```
385
+ Will generate:
386
+ ```bash
387
+ ./app/controllers/apples_controller.rb # Controller with respective action
388
+ ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
389
+ ./app/models/apple.rb # Model that acts as ORM
390
+ ./db/create_apples.rb # Database migration file needed for creating repsective table
391
+ ```
392
+
393
+ Example 3:
394
+ ```ruby
395
+ Rubee::Router.draw do |router|
396
+ ...
397
+ # draw the contract
398
+ router.get "/apples", to: "apples#index",
399
+ model: {
400
+ name: 'apple',
401
+ attributes: [
402
+ { name: 'id', type: :primary },
403
+ { name: 'colour', type: :string },
404
+ { name: 'weight', type: :integer }
405
+ ]
406
+ }
407
+ end
408
+ ```
409
+
410
+ Will generate:
411
+ ```bash
412
+ ./app/controllers/apples_controller.rb # Controller with respective action
413
+ ./app/models/apple.rb # Model that acts as ORM
414
+ ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
415
+ ./db/create_apples.rb # Database migration file needed for creating repsective table
416
+ ```
280
417
 
281
418
  ## Views
282
- View in ruBee is just a plain html/erb file that can be rendered from the controller.
419
+ View in ruBee is just a plain html/erb/react file that can be rendered from the controller.
283
420
 
284
421
  ## Templates over erb
285
422
 
@@ -369,7 +506,7 @@ end
369
506
  # app/controllers/api/user_controller.rb
370
507
  class Api::UserController < Rubee::BaseController
371
508
  def index
372
- response_with object: User.all
509
+ response_with object: User.all, type: :json
373
510
  end
374
511
  end
375
512
  ```
@@ -379,7 +516,7 @@ end
379
516
  // app/views/app.tsx
380
517
  <Router>
381
518
  <Routes>
382
- <Route path="/users" element={<Home />} />
519
+ <Route path="/users" element={<Users />} />
383
520
  <Route path="*" element={<NotFound />} />
384
521
  </Routes>
385
522
  </Router>
@@ -508,6 +645,8 @@ end
508
645
  ```bash
509
646
  rubee start # start the server
510
647
  rubee start_dev # start the server in dev mode, which restart server on changes
648
+ rubee react prepare # install react dependencies
649
+ rubee react watch # dev mode for react, works together with start_dev
511
650
  rubee stop # stop the server
512
651
  rubee restart # restart the server
513
652
  ```
@@ -519,6 +658,7 @@ rubee generate get /apples # generate controller view, model and migration if se
519
658
 
520
659
  ## Migraiton commands
521
660
  ```bash
661
+ rubee db run:all # run all migrations
522
662
  rubee db run:create_apples # where create_apples is the name of the migration file, located in /db folder
523
663
  rubee db structure # generate migration file for the database structure
524
664
  ```
@@ -526,6 +666,7 @@ rubee db structure # generate migration file for the database structure
526
666
  ## Rubee console
527
667
  ```bash
528
668
  rubee console # start the console
669
+ # you can reload the console by typing reload, so it will pick up latest changes
529
670
  ```
530
671
 
531
672
  ## Testing
@@ -533,6 +674,7 @@ rubee console # start the console
533
674
  rubee test # run all tests
534
675
  rubee test auth_tokenable_test.rb # run specific tests
535
676
  ```
677
+
536
678
  If you want to run any ruBee command within a specific ENV make sure you added it before a command.
537
679
  For instance if you want to run console in test environment you need to run the following command
538
680
 
@@ -616,30 +758,11 @@ TestAsyncRunnner.new.perform_async(options: {"email"=> "new@new.com", "password"
616
758
 
617
759
  ### Contributing
618
760
 
619
- You are more than welcome to contribute to ruBee! To do so, please follow these steps:
620
-
621
- 1. Fork the repository by clicking the "Fork" button on the GitHub page.
622
-
623
- 2. Clone your fork:
624
- ```bash
625
- git clone https://github.com/your-username/rubee.git
626
- ```
627
-
628
- 3. Create a new branch for your feature or bug fix:
629
- ```bash
630
- git checkout -b feature/your-feature-name
631
- ```
632
-
633
- 4. Make your changes and commit them with descriptive messages:
634
- ```bash
635
- git commit -m "Add feature: [brief description of feature]"
636
- ```
637
-
638
- 5. Push your changes to your fork:
639
- ```bash
640
- git push origin feature/your-feature-name
641
- ```
642
-
643
- 6. Submit a pull request to the main branch of the original repository.
761
+ If you are interested in contributing to ruBee,
762
+ please read the [Contributing](https://github.com/nucleom42/rubee/blob/main/CONTRIBUTING.md) guide.
763
+ Also feel free to open an [issue](https://github.com/nucleom42/rubee/issues) if you apot one.
764
+ Have an idea or you wnat to discuss something?
765
+ Please open a [discussion](https://github.com/nucleom42/rubee/discussions)
644
766
 
645
- Let's make it shine even brighter!
767
+ ## License
768
+ This project is released under the MIT License.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ru.Bee
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Saltykov
@@ -44,14 +44,17 @@ files:
44
44
  - lib/app/controllers/welcome_controller.rb
45
45
  - lib/app/models/user.rb
46
46
  - lib/app/views/app.tsx
47
+ - lib/app/views/apples_.erb
47
48
  - lib/app/views/index.html
48
49
  - lib/app/views/layout.erb
50
+ - lib/app/views/s_.erb
49
51
  - lib/app/views/utils/redirectToBackend.tsx
50
52
  - lib/app/views/welcome_header.erb
51
53
  - lib/app/views/welcome_show.erb
52
54
  - lib/config.ru
53
55
  - lib/config/base_configuration.rb
54
56
  - lib/config/routes.rb
57
+ - lib/css/app.css
55
58
  - lib/db/create_accounts.rb
56
59
  - lib/db/create_comments.rb
57
60
  - lib/db/create_posts.rb
@@ -59,6 +62,7 @@ files:
59
62
  - lib/db/structure.rb
60
63
  - lib/esbuild.config.js
61
64
  - lib/images/rubee.svg
65
+ - lib/inits/charged_string.rb
62
66
  - lib/inits/print_colors.rb
63
67
  - lib/js/app.js
64
68
  - lib/js/app.js.map
@@ -243,6 +247,7 @@ files:
243
247
  - lib/tests/example_models/comment.rb
244
248
  - lib/tests/example_models/post.rb
245
249
  - lib/tests/example_models/user.rb
250
+ - lib/tests/rubee_generator_test.rb
246
251
  - lib/tests/rubeeapp_test.rb
247
252
  - lib/tests/test.db
248
253
  - lib/tests/test_helper.rb