tina4ruby 3.11.13 → 3.11.15
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/CHANGELOG.md +80 -80
- data/LICENSE.txt +21 -21
- data/README.md +137 -137
- data/exe/tina4ruby +5 -5
- data/lib/tina4/ai.rb +696 -696
- data/lib/tina4/api.rb +189 -189
- data/lib/tina4/auth.rb +305 -305
- data/lib/tina4/auto_crud.rb +244 -244
- data/lib/tina4/cache.rb +154 -154
- data/lib/tina4/cli.rb +1449 -1449
- data/lib/tina4/constants.rb +46 -46
- data/lib/tina4/container.rb +74 -74
- data/lib/tina4/cors.rb +74 -74
- data/lib/tina4/crud.rb +692 -692
- data/lib/tina4/database/sqlite3_adapter.rb +165 -165
- data/lib/tina4/database.rb +625 -625
- data/lib/tina4/database_result.rb +208 -208
- data/lib/tina4/debug.rb +8 -8
- data/lib/tina4/dev.rb +14 -14
- data/lib/tina4/dev_admin.rb +935 -935
- data/lib/tina4/dev_mailbox.rb +191 -191
- data/lib/tina4/drivers/firebird_driver.rb +124 -110
- data/lib/tina4/drivers/mongodb_driver.rb +561 -561
- data/lib/tina4/drivers/mssql_driver.rb +112 -112
- data/lib/tina4/drivers/mysql_driver.rb +90 -90
- data/lib/tina4/drivers/odbc_driver.rb +191 -191
- data/lib/tina4/drivers/postgres_driver.rb +116 -106
- data/lib/tina4/drivers/sqlite_driver.rb +122 -122
- data/lib/tina4/env.rb +95 -95
- data/lib/tina4/error_overlay.rb +252 -252
- data/lib/tina4/events.rb +109 -109
- data/lib/tina4/field_types.rb +154 -154
- data/lib/tina4/frond.rb +2025 -2025
- data/lib/tina4/gallery/auth/meta.json +1 -1
- data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -114
- data/lib/tina4/gallery/database/meta.json +1 -1
- data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -43
- data/lib/tina4/gallery/error-overlay/meta.json +1 -1
- data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -17
- data/lib/tina4/gallery/orm/meta.json +1 -1
- data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -16
- data/lib/tina4/gallery/queue/meta.json +1 -1
- data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -325
- data/lib/tina4/gallery/rest-api/meta.json +1 -1
- data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -14
- data/lib/tina4/gallery/templates/meta.json +1 -1
- data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -12
- data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -257
- data/lib/tina4/graphql.rb +966 -966
- data/lib/tina4/health.rb +39 -39
- data/lib/tina4/html_element.rb +170 -170
- data/lib/tina4/job.rb +80 -80
- data/lib/tina4/localization.rb +168 -168
- data/lib/tina4/log.rb +203 -203
- data/lib/tina4/mcp.rb +696 -696
- data/lib/tina4/messenger.rb +587 -587
- data/lib/tina4/metrics.rb +793 -793
- data/lib/tina4/middleware.rb +445 -445
- data/lib/tina4/migration.rb +451 -451
- data/lib/tina4/orm.rb +790 -790
- data/lib/tina4/public/css/tina4.css +2463 -2463
- data/lib/tina4/public/css/tina4.min.css +1 -1
- data/lib/tina4/public/images/logo.svg +5 -5
- data/lib/tina4/public/js/frond.min.js +2 -2
- data/lib/tina4/public/js/tina4-dev-admin.js +565 -565
- data/lib/tina4/public/js/tina4-dev-admin.min.js +480 -480
- data/lib/tina4/public/js/tina4.min.js +92 -92
- data/lib/tina4/public/js/tina4js.min.js +48 -48
- data/lib/tina4/public/swagger/index.html +90 -90
- data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
- data/lib/tina4/query_builder.rb +380 -380
- data/lib/tina4/queue.rb +366 -366
- data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
- data/lib/tina4/queue_backends/lite_backend.rb +298 -298
- data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
- data/lib/tina4/rack_app.rb +817 -817
- data/lib/tina4/rate_limiter.rb +130 -130
- data/lib/tina4/request.rb +268 -255
- data/lib/tina4/response.rb +346 -346
- data/lib/tina4/response_cache.rb +551 -551
- data/lib/tina4/router.rb +406 -406
- data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
- data/lib/tina4/scss/tina4css/_badges.scss +22 -22
- data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
- data/lib/tina4/scss/tina4css/_cards.scss +49 -49
- data/lib/tina4/scss/tina4css/_forms.scss +156 -156
- data/lib/tina4/scss/tina4css/_grid.scss +81 -81
- data/lib/tina4/scss/tina4css/_modals.scss +84 -84
- data/lib/tina4/scss/tina4css/_nav.scss +149 -149
- data/lib/tina4/scss/tina4css/_reset.scss +94 -94
- data/lib/tina4/scss/tina4css/_tables.scss +54 -54
- data/lib/tina4/scss/tina4css/_typography.scss +55 -55
- data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
- data/lib/tina4/scss/tina4css/_variables.scss +117 -117
- data/lib/tina4/scss/tina4css/base.scss +1 -1
- data/lib/tina4/scss/tina4css/colors.scss +48 -48
- data/lib/tina4/scss/tina4css/tina4.scss +17 -17
- data/lib/tina4/scss_compiler.rb +178 -178
- data/lib/tina4/seeder.rb +567 -567
- data/lib/tina4/service_runner.rb +303 -303
- data/lib/tina4/session.rb +297 -297
- data/lib/tina4/session_handlers/database_handler.rb +72 -72
- data/lib/tina4/session_handlers/file_handler.rb +67 -67
- data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
- data/lib/tina4/session_handlers/redis_handler.rb +43 -43
- data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
- data/lib/tina4/shutdown.rb +84 -84
- data/lib/tina4/sql_translation.rb +158 -158
- data/lib/tina4/swagger.rb +124 -124
- data/lib/tina4/template.rb +894 -894
- data/lib/tina4/templates/base.twig +26 -26
- data/lib/tina4/templates/errors/302.twig +14 -14
- data/lib/tina4/templates/errors/401.twig +9 -9
- data/lib/tina4/templates/errors/403.twig +29 -29
- data/lib/tina4/templates/errors/404.twig +29 -29
- data/lib/tina4/templates/errors/500.twig +38 -38
- data/lib/tina4/templates/errors/502.twig +9 -9
- data/lib/tina4/templates/errors/503.twig +12 -12
- data/lib/tina4/templates/errors/base.twig +37 -37
- data/lib/tina4/test_client.rb +159 -159
- data/lib/tina4/testing.rb +340 -340
- data/lib/tina4/validator.rb +174 -174
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +312 -312
- data/lib/tina4/websocket.rb +343 -343
- data/lib/tina4/websocket_backplane.rb +190 -190
- data/lib/tina4/wsdl.rb +564 -564
- data/lib/tina4.rb +458 -458
- data/lib/tina4ruby.rb +4 -4
- metadata +3 -3
data/lib/tina4/events.rb
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Tina4 Events — Simple observer pattern for decoupled communication.
|
|
4
|
-
#
|
|
5
|
-
# Zero-dependency event system. Fire events, register listeners.
|
|
6
|
-
#
|
|
7
|
-
# Tina4::Events.on("user.created") { |user| puts "Welcome #{user[:name]}!" }
|
|
8
|
-
# Tina4::Events.on("user.created") { |user| puts "New signup: #{user[:email]}" }
|
|
9
|
-
# Tina4::Events.emit("user.created", { name: "Alice", email: "alice@example.com" })
|
|
10
|
-
#
|
|
11
|
-
# One-time listeners:
|
|
12
|
-
#
|
|
13
|
-
# Tina4::Events.once("app.ready") { puts "App started!" }
|
|
14
|
-
#
|
|
15
|
-
module Tina4
|
|
16
|
-
class Events
|
|
17
|
-
@listeners = Hash.new { |h, k| h[k] = [] }
|
|
18
|
-
|
|
19
|
-
class << self
|
|
20
|
-
# Register a listener for an event.
|
|
21
|
-
#
|
|
22
|
-
# Tina4::Events.on("user.created") { |user| ... }
|
|
23
|
-
# Tina4::Events.on("user.created", priority: 10) { |user| ... }
|
|
24
|
-
#
|
|
25
|
-
# Higher priority runs first.
|
|
26
|
-
def on(event, priority: 0, &block)
|
|
27
|
-
raise ArgumentError, "block required" unless block_given?
|
|
28
|
-
|
|
29
|
-
@listeners[event] << { priority: priority, callback: block, once: false }
|
|
30
|
-
@listeners[event].sort_by! { |entry| -entry[:priority] }
|
|
31
|
-
block
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Register a listener that fires only once then auto-removes.
|
|
35
|
-
#
|
|
36
|
-
# Tina4::Events.once("app.ready") { puts "App started!" }
|
|
37
|
-
#
|
|
38
|
-
def once(event, priority: 0, &block)
|
|
39
|
-
raise ArgumentError, "block required" unless block_given?
|
|
40
|
-
|
|
41
|
-
@listeners[event] << { priority: priority, callback: block, once: true }
|
|
42
|
-
@listeners[event].sort_by! { |entry| -entry[:priority] }
|
|
43
|
-
block
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Remove a specific listener, or all listeners for an event.
|
|
47
|
-
#
|
|
48
|
-
# Tina4::Events.off("user.created", handler) # remove specific
|
|
49
|
-
# Tina4::Events.off("user.created") # remove all for event
|
|
50
|
-
#
|
|
51
|
-
def off(event, callback = nil)
|
|
52
|
-
if callback.nil?
|
|
53
|
-
@listeners.delete(event)
|
|
54
|
-
else
|
|
55
|
-
@listeners[event].reject! { |entry| entry[:callback] == callback }
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Fire an event synchronously. Returns array of listener results.
|
|
60
|
-
#
|
|
61
|
-
# results = Tina4::Events.emit("user.created", user_data)
|
|
62
|
-
#
|
|
63
|
-
def emit(event, *args)
|
|
64
|
-
entries = @listeners[event].dup
|
|
65
|
-
results = []
|
|
66
|
-
entries.each do |entry|
|
|
67
|
-
# Remove one-time listeners before calling so re-entrant emits are safe
|
|
68
|
-
@listeners[event].delete(entry) if entry[:once]
|
|
69
|
-
results << entry[:callback].call(*args)
|
|
70
|
-
end
|
|
71
|
-
results
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Get all listener callbacks for an event (in priority order).
|
|
75
|
-
def listeners(event)
|
|
76
|
-
@listeners[event].map { |entry| entry[:callback] }
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# List all registered event names.
|
|
80
|
-
def events
|
|
81
|
-
@listeners.keys
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Fire an event asynchronously. Each listener runs in its own thread.
|
|
85
|
-
# Errors in listeners are silently caught.
|
|
86
|
-
#
|
|
87
|
-
# Tina4::Events.emit_async("user.created", user_data)
|
|
88
|
-
#
|
|
89
|
-
def emit_async(event, *args)
|
|
90
|
-
return unless @listeners&.key?(event)
|
|
91
|
-
|
|
92
|
-
@listeners[event].sort_by { |l| -(l[:priority] || 0) }.each do |listener|
|
|
93
|
-
Thread.new do
|
|
94
|
-
begin
|
|
95
|
-
listener[:callback].call(*args)
|
|
96
|
-
rescue => e
|
|
97
|
-
# Async emit silently catches errors
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Remove all listeners for all events.
|
|
104
|
-
def clear
|
|
105
|
-
@listeners.clear
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Tina4 Events — Simple observer pattern for decoupled communication.
|
|
4
|
+
#
|
|
5
|
+
# Zero-dependency event system. Fire events, register listeners.
|
|
6
|
+
#
|
|
7
|
+
# Tina4::Events.on("user.created") { |user| puts "Welcome #{user[:name]}!" }
|
|
8
|
+
# Tina4::Events.on("user.created") { |user| puts "New signup: #{user[:email]}" }
|
|
9
|
+
# Tina4::Events.emit("user.created", { name: "Alice", email: "alice@example.com" })
|
|
10
|
+
#
|
|
11
|
+
# One-time listeners:
|
|
12
|
+
#
|
|
13
|
+
# Tina4::Events.once("app.ready") { puts "App started!" }
|
|
14
|
+
#
|
|
15
|
+
module Tina4
|
|
16
|
+
class Events
|
|
17
|
+
@listeners = Hash.new { |h, k| h[k] = [] }
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# Register a listener for an event.
|
|
21
|
+
#
|
|
22
|
+
# Tina4::Events.on("user.created") { |user| ... }
|
|
23
|
+
# Tina4::Events.on("user.created", priority: 10) { |user| ... }
|
|
24
|
+
#
|
|
25
|
+
# Higher priority runs first.
|
|
26
|
+
def on(event, priority: 0, &block)
|
|
27
|
+
raise ArgumentError, "block required" unless block_given?
|
|
28
|
+
|
|
29
|
+
@listeners[event] << { priority: priority, callback: block, once: false }
|
|
30
|
+
@listeners[event].sort_by! { |entry| -entry[:priority] }
|
|
31
|
+
block
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Register a listener that fires only once then auto-removes.
|
|
35
|
+
#
|
|
36
|
+
# Tina4::Events.once("app.ready") { puts "App started!" }
|
|
37
|
+
#
|
|
38
|
+
def once(event, priority: 0, &block)
|
|
39
|
+
raise ArgumentError, "block required" unless block_given?
|
|
40
|
+
|
|
41
|
+
@listeners[event] << { priority: priority, callback: block, once: true }
|
|
42
|
+
@listeners[event].sort_by! { |entry| -entry[:priority] }
|
|
43
|
+
block
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Remove a specific listener, or all listeners for an event.
|
|
47
|
+
#
|
|
48
|
+
# Tina4::Events.off("user.created", handler) # remove specific
|
|
49
|
+
# Tina4::Events.off("user.created") # remove all for event
|
|
50
|
+
#
|
|
51
|
+
def off(event, callback = nil)
|
|
52
|
+
if callback.nil?
|
|
53
|
+
@listeners.delete(event)
|
|
54
|
+
else
|
|
55
|
+
@listeners[event].reject! { |entry| entry[:callback] == callback }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Fire an event synchronously. Returns array of listener results.
|
|
60
|
+
#
|
|
61
|
+
# results = Tina4::Events.emit("user.created", user_data)
|
|
62
|
+
#
|
|
63
|
+
def emit(event, *args)
|
|
64
|
+
entries = @listeners[event].dup
|
|
65
|
+
results = []
|
|
66
|
+
entries.each do |entry|
|
|
67
|
+
# Remove one-time listeners before calling so re-entrant emits are safe
|
|
68
|
+
@listeners[event].delete(entry) if entry[:once]
|
|
69
|
+
results << entry[:callback].call(*args)
|
|
70
|
+
end
|
|
71
|
+
results
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get all listener callbacks for an event (in priority order).
|
|
75
|
+
def listeners(event)
|
|
76
|
+
@listeners[event].map { |entry| entry[:callback] }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# List all registered event names.
|
|
80
|
+
def events
|
|
81
|
+
@listeners.keys
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Fire an event asynchronously. Each listener runs in its own thread.
|
|
85
|
+
# Errors in listeners are silently caught.
|
|
86
|
+
#
|
|
87
|
+
# Tina4::Events.emit_async("user.created", user_data)
|
|
88
|
+
#
|
|
89
|
+
def emit_async(event, *args)
|
|
90
|
+
return unless @listeners&.key?(event)
|
|
91
|
+
|
|
92
|
+
@listeners[event].sort_by { |l| -(l[:priority] || 0) }.each do |listener|
|
|
93
|
+
Thread.new do
|
|
94
|
+
begin
|
|
95
|
+
listener[:callback].call(*args)
|
|
96
|
+
rescue => e
|
|
97
|
+
# Async emit silently catches errors
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Remove all listeners for all events.
|
|
104
|
+
def clear
|
|
105
|
+
@listeners.clear
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/tina4/field_types.rb
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tina4
|
|
4
|
-
module FieldTypes
|
|
5
|
-
def self.included(base)
|
|
6
|
-
base.extend(ClassMethods)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
module ClassMethods
|
|
10
|
-
def field_definitions
|
|
11
|
-
@field_definitions ||= {}
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def primary_key_field
|
|
15
|
-
@primary_key_field
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def table_name(name = nil)
|
|
19
|
-
if name
|
|
20
|
-
@table_name = name
|
|
21
|
-
else
|
|
22
|
-
base = self.name.split("::").last.downcase
|
|
23
|
-
# Pluralize by default (add "s") unless ORM_PLURAL_TABLE_NAMES is explicitly disabled
|
|
24
|
-
unless ENV.fetch("ORM_PLURAL_TABLE_NAMES", "").match?(/\A(false|0|no)\z/i)
|
|
25
|
-
base += "s" unless base.end_with?("s")
|
|
26
|
-
end
|
|
27
|
-
@table_name || base
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def integer_field(name, primary_key: false, auto_increment: false, nullable: true, default: nil)
|
|
32
|
-
register_field(name, :integer, primary_key: primary_key, auto_increment: auto_increment,
|
|
33
|
-
nullable: nullable, default: default)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def string_field(name, length: 255, primary_key: false, nullable: true, default: nil)
|
|
37
|
-
register_field(name, :string, length: length, primary_key: primary_key,
|
|
38
|
-
nullable: nullable, default: default)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def text_field(name, nullable: true, default: nil)
|
|
42
|
-
register_field(name, :text, nullable: nullable, default: default)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def float_field(name, nullable: true, default: nil)
|
|
46
|
-
register_field(name, :float, nullable: nullable, default: default)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def decimal_field(name, precision: 10, scale: 2, nullable: true, default: nil)
|
|
50
|
-
register_field(name, :decimal, precision: precision, scale: scale,
|
|
51
|
-
nullable: nullable, default: default)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def numeric_field(name, nullable: true, default: nil)
|
|
55
|
-
register_field(name, :float, nullable: nullable, default: default)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def boolean_field(name, nullable: true, default: nil)
|
|
59
|
-
register_field(name, :boolean, nullable: nullable, default: default)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def date_field(name, nullable: true, default: nil)
|
|
63
|
-
register_field(name, :date, nullable: nullable, default: default)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def datetime_field(name, nullable: true, default: nil)
|
|
67
|
-
register_field(name, :datetime, nullable: nullable, default: default)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def timestamp_field(name, nullable: true, default: nil)
|
|
71
|
-
register_field(name, :timestamp, nullable: nullable, default: default)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def blob_field(name, nullable: true, default: nil)
|
|
75
|
-
register_field(name, :blob, nullable: nullable, default: default)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def json_field(name, nullable: true, default: nil)
|
|
79
|
-
register_field(name, :json, nullable: nullable, default: default)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Declare a foreign key integer column and auto-wire relationships.
|
|
83
|
-
#
|
|
84
|
-
# Automatically:
|
|
85
|
-
# - Registers an integer field for the column
|
|
86
|
-
# - Calls belongs_to on this class (strip _id suffix for association name)
|
|
87
|
-
# - Calls has_many on the referenced class (if already loaded)
|
|
88
|
-
#
|
|
89
|
-
# @param name [Symbol] Column name (e.g. :user_id)
|
|
90
|
-
# @param references [Class, String] Referenced model class or its name
|
|
91
|
-
# @param related_name [Symbol, String, nil] Override the has-many name on the referenced model
|
|
92
|
-
#
|
|
93
|
-
# Example:
|
|
94
|
-
# class Post < Tina4::ORM
|
|
95
|
-
# integer_field :id, primary_key: true
|
|
96
|
-
# foreign_key_field :user_id, references: User
|
|
97
|
-
# end
|
|
98
|
-
# # post.user → belongs_to auto-wired
|
|
99
|
-
# # user.posts → has_many auto-wired
|
|
100
|
-
def foreign_key_field(name, references:, related_name: nil, **options)
|
|
101
|
-
register_field(name, :integer, **options)
|
|
102
|
-
|
|
103
|
-
# Derive association name: strip _id suffix
|
|
104
|
-
belongs_name = name.to_s.end_with?("_id") ? name.to_s[0..-4].to_sym : name.to_sym
|
|
105
|
-
|
|
106
|
-
# Wire belongs_to on this class
|
|
107
|
-
belongs_to(belongs_name, class_name: references.to_s.split("::").last, foreign_key: name.to_s) if respond_to?(:belongs_to, true)
|
|
108
|
-
|
|
109
|
-
# Wire has_many on referenced class (if already a loaded Class)
|
|
110
|
-
if references.is_a?(Class) && references.respond_to?(:has_many, true)
|
|
111
|
-
hm_name = (related_name || "#{self.name.split("::").last.downcase}s").to_sym
|
|
112
|
-
references.has_many(hm_name, class_name: self.name.split("::").last, foreign_key: name.to_s)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Register for deferred wiring (resolves when referenced class is later loaded)
|
|
116
|
-
@@_fk_registry ||= {}
|
|
117
|
-
ref_name = references.is_a?(Class) ? references.name.split("::").last : references.to_s.split("::").last
|
|
118
|
-
@@_fk_registry[ref_name] ||= []
|
|
119
|
-
hm_key = (related_name || "#{self.name.split("::").last.downcase}s").to_s
|
|
120
|
-
@@_fk_registry[ref_name] << {
|
|
121
|
-
declaring_class: self,
|
|
122
|
-
has_many_name: hm_key.to_sym,
|
|
123
|
-
foreign_key: name.to_s
|
|
124
|
-
}
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Apply any deferred FK-registry has_many wiring for this class.
|
|
128
|
-
# Called automatically when a class that is referenced by a ForeignKeyField is defined.
|
|
129
|
-
def apply_fk_registry!
|
|
130
|
-
class_simple_name = self.name.split("::").last
|
|
131
|
-
return unless defined?(@@_fk_registry) && @@_fk_registry.key?(class_simple_name)
|
|
132
|
-
|
|
133
|
-
@@_fk_registry[class_simple_name].each do |entry|
|
|
134
|
-
next if entry[:applied]
|
|
135
|
-
|
|
136
|
-
has_many(entry[:has_many_name],
|
|
137
|
-
class_name: entry[:declaring_class].name.split("::").last,
|
|
138
|
-
foreign_key: entry[:foreign_key]) if respond_to?(:has_many, true)
|
|
139
|
-
entry[:applied] = true
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
private
|
|
144
|
-
|
|
145
|
-
def register_field(name, type, **options)
|
|
146
|
-
field_definitions[name] = { type: type }.merge(options)
|
|
147
|
-
@primary_key_field = name if options[:primary_key]
|
|
148
|
-
|
|
149
|
-
# Define getter/setter
|
|
150
|
-
attr_accessor name
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tina4
|
|
4
|
+
module FieldTypes
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def field_definitions
|
|
11
|
+
@field_definitions ||= {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def primary_key_field
|
|
15
|
+
@primary_key_field
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def table_name(name = nil)
|
|
19
|
+
if name
|
|
20
|
+
@table_name = name
|
|
21
|
+
else
|
|
22
|
+
base = self.name.split("::").last.downcase
|
|
23
|
+
# Pluralize by default (add "s") unless ORM_PLURAL_TABLE_NAMES is explicitly disabled
|
|
24
|
+
unless ENV.fetch("ORM_PLURAL_TABLE_NAMES", "").match?(/\A(false|0|no)\z/i)
|
|
25
|
+
base += "s" unless base.end_with?("s")
|
|
26
|
+
end
|
|
27
|
+
@table_name || base
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def integer_field(name, primary_key: false, auto_increment: false, nullable: true, default: nil)
|
|
32
|
+
register_field(name, :integer, primary_key: primary_key, auto_increment: auto_increment,
|
|
33
|
+
nullable: nullable, default: default)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def string_field(name, length: 255, primary_key: false, nullable: true, default: nil)
|
|
37
|
+
register_field(name, :string, length: length, primary_key: primary_key,
|
|
38
|
+
nullable: nullable, default: default)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def text_field(name, nullable: true, default: nil)
|
|
42
|
+
register_field(name, :text, nullable: nullable, default: default)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def float_field(name, nullable: true, default: nil)
|
|
46
|
+
register_field(name, :float, nullable: nullable, default: default)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def decimal_field(name, precision: 10, scale: 2, nullable: true, default: nil)
|
|
50
|
+
register_field(name, :decimal, precision: precision, scale: scale,
|
|
51
|
+
nullable: nullable, default: default)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def numeric_field(name, nullable: true, default: nil)
|
|
55
|
+
register_field(name, :float, nullable: nullable, default: default)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def boolean_field(name, nullable: true, default: nil)
|
|
59
|
+
register_field(name, :boolean, nullable: nullable, default: default)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def date_field(name, nullable: true, default: nil)
|
|
63
|
+
register_field(name, :date, nullable: nullable, default: default)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def datetime_field(name, nullable: true, default: nil)
|
|
67
|
+
register_field(name, :datetime, nullable: nullable, default: default)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def timestamp_field(name, nullable: true, default: nil)
|
|
71
|
+
register_field(name, :timestamp, nullable: nullable, default: default)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def blob_field(name, nullable: true, default: nil)
|
|
75
|
+
register_field(name, :blob, nullable: nullable, default: default)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def json_field(name, nullable: true, default: nil)
|
|
79
|
+
register_field(name, :json, nullable: nullable, default: default)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Declare a foreign key integer column and auto-wire relationships.
|
|
83
|
+
#
|
|
84
|
+
# Automatically:
|
|
85
|
+
# - Registers an integer field for the column
|
|
86
|
+
# - Calls belongs_to on this class (strip _id suffix for association name)
|
|
87
|
+
# - Calls has_many on the referenced class (if already loaded)
|
|
88
|
+
#
|
|
89
|
+
# @param name [Symbol] Column name (e.g. :user_id)
|
|
90
|
+
# @param references [Class, String] Referenced model class or its name
|
|
91
|
+
# @param related_name [Symbol, String, nil] Override the has-many name on the referenced model
|
|
92
|
+
#
|
|
93
|
+
# Example:
|
|
94
|
+
# class Post < Tina4::ORM
|
|
95
|
+
# integer_field :id, primary_key: true
|
|
96
|
+
# foreign_key_field :user_id, references: User
|
|
97
|
+
# end
|
|
98
|
+
# # post.user → belongs_to auto-wired
|
|
99
|
+
# # user.posts → has_many auto-wired
|
|
100
|
+
def foreign_key_field(name, references:, related_name: nil, **options)
|
|
101
|
+
register_field(name, :integer, **options)
|
|
102
|
+
|
|
103
|
+
# Derive association name: strip _id suffix
|
|
104
|
+
belongs_name = name.to_s.end_with?("_id") ? name.to_s[0..-4].to_sym : name.to_sym
|
|
105
|
+
|
|
106
|
+
# Wire belongs_to on this class
|
|
107
|
+
belongs_to(belongs_name, class_name: references.to_s.split("::").last, foreign_key: name.to_s) if respond_to?(:belongs_to, true)
|
|
108
|
+
|
|
109
|
+
# Wire has_many on referenced class (if already a loaded Class)
|
|
110
|
+
if references.is_a?(Class) && references.respond_to?(:has_many, true)
|
|
111
|
+
hm_name = (related_name || "#{self.name.split("::").last.downcase}s").to_sym
|
|
112
|
+
references.has_many(hm_name, class_name: self.name.split("::").last, foreign_key: name.to_s)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Register for deferred wiring (resolves when referenced class is later loaded)
|
|
116
|
+
@@_fk_registry ||= {}
|
|
117
|
+
ref_name = references.is_a?(Class) ? references.name.split("::").last : references.to_s.split("::").last
|
|
118
|
+
@@_fk_registry[ref_name] ||= []
|
|
119
|
+
hm_key = (related_name || "#{self.name.split("::").last.downcase}s").to_s
|
|
120
|
+
@@_fk_registry[ref_name] << {
|
|
121
|
+
declaring_class: self,
|
|
122
|
+
has_many_name: hm_key.to_sym,
|
|
123
|
+
foreign_key: name.to_s
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Apply any deferred FK-registry has_many wiring for this class.
|
|
128
|
+
# Called automatically when a class that is referenced by a ForeignKeyField is defined.
|
|
129
|
+
def apply_fk_registry!
|
|
130
|
+
class_simple_name = self.name.split("::").last
|
|
131
|
+
return unless defined?(@@_fk_registry) && @@_fk_registry.key?(class_simple_name)
|
|
132
|
+
|
|
133
|
+
@@_fk_registry[class_simple_name].each do |entry|
|
|
134
|
+
next if entry[:applied]
|
|
135
|
+
|
|
136
|
+
has_many(entry[:has_many_name],
|
|
137
|
+
class_name: entry[:declaring_class].name.split("::").last,
|
|
138
|
+
foreign_key: entry[:foreign_key]) if respond_to?(:has_many, true)
|
|
139
|
+
entry[:applied] = true
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def register_field(name, type, **options)
|
|
146
|
+
field_definitions[name] = { type: type }.merge(options)
|
|
147
|
+
@primary_key_field = name if options[:primary_key]
|
|
148
|
+
|
|
149
|
+
# Define getter/setter
|
|
150
|
+
attr_accessor name
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|