tina4ruby 3.13.18 → 3.13.19

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: 0073552dcac0e437fa36de2038f6958cbdbcde9077ec2427d9fd1bf77242da0e
4
- data.tar.gz: c5c667382a67031bef1d7f3646020984942079edb5de9c5084c135ce4be68e9d
3
+ metadata.gz: 93597b3b5afd4c56593d4c2d107b573790b68fbae4b0dd28f07d4f636a462381
4
+ data.tar.gz: c384790499ba27f16fc95efec1053a34c201d5444f742a9581167f6450418526
5
5
  SHA512:
6
- metadata.gz: d7e9036bfe01826691e30382e35b8c4f70dfe2043602f41abae7a0e573993992a99f89e733b2b7dd74e4c2c2d6fe1c6be7faeb4d57d132d0be6c1d471f538afb
7
- data.tar.gz: 7a4ec3bdd62b40c973e2fadbd52f205d92bc6fd2f0e18c1407a164a6cb27fcb03bffb50ec789851ac567105c1da81c6fbdcd00ece6fdbefef2411d1067f55610
6
+ metadata.gz: fd30dce3ddb8de6ec35fa4416f105b52df238951d723df7887ad24787a9e657bd04c90e904427dae4365faf602ef273cfb1716dcd655b15b0ac0caf45ecfa4d0
7
+ data.tar.gz: 1febfd89b40487edc5c51314d6bd5878ecd22244720959e58f54e56d90f2751d0543be833f4f0ddb2da51bcdee5ba8aa5d3c06d6723fe28df644ae4f6f2ef3db
data/lib/tina4/orm.rb CHANGED
@@ -41,14 +41,32 @@ module Tina4
41
41
 
42
42
  class << self
43
43
  def db
44
- # v3.13.12: implicit binding from TINA4_DATABASE_URL.
45
- # Resolution: per-class @db → global Tina4.database → env-derived
46
- # auto-discovery. Pre-v3.13.12 this fell through to nil — the
47
- # helper auto_discover_db existed but was never called.
48
- @db || Tina4.database || auto_discover_db
44
+ # Resolution order:
45
+ # 1. @db is a Symbol/String named connection from Tina4.databases
46
+ # (bound via Tina4.bind_database(db, name:)). Raises a clear
47
+ # error if that named connection was never registered.
48
+ # 2. @db is a Database/driver instance → use it directly.
49
+ # 3. Otherwise → global Tina4.database, else env-derived
50
+ # auto-discovery (TINA4_DATABASE_URL). v3.13.12 wired this
51
+ # fallback; before that auto_discover_db was never called.
52
+ case @db
53
+ when Symbol, String
54
+ name = @db.to_sym
55
+ Tina4.databases[name] || raise(
56
+ "Tina4 named database connection '#{@db}' is not registered for #{name}. " \
57
+ "Call Tina4.bind_database(db, name: #{@db.inspect}) before using this model."
58
+ )
59
+ when nil
60
+ Tina4.database || auto_discover_db
61
+ else
62
+ @db
63
+ end
49
64
  end
50
65
 
51
- # Per-model database binding
66
+ # Per-model database binding.
67
+ # self.db = some_database_instance → use that connection
68
+ # self.db = :analytics → resolve a named connection
69
+ # from Tina4.databases at access time
52
70
  def db=(database)
53
71
  @db = database
54
72
  end
@@ -476,8 +494,7 @@ module Tina4
476
494
  def auto_discover_db
477
495
  url = ENV["TINA4_DATABASE_URL"]
478
496
  return nil unless url
479
- Tina4.database = Tina4::Database.new(url, username: ENV.fetch("TINA4_DATABASE_USERNAME", ""), password: ENV.fetch("TINA4_DATABASE_PASSWORD", ""))
480
- Tina4.database
497
+ Tina4.bind_database(Tina4::Database.new(url, username: ENV.fetch("TINA4_DATABASE_USERNAME", ""), password: ENV.fetch("TINA4_DATABASE_PASSWORD", "")))
481
498
  end
482
499
 
483
500
  def find_by_filter(filter)
@@ -517,6 +534,15 @@ module Tina4
517
534
  @persisted = false
518
535
  @errors = []
519
536
  @relationship_cache = {}
537
+ # Accept a JSON object string (parity with Python/PHP/Node):
538
+ # Widget.new('{"id":1,"name":"alpha"}')
539
+ attributes = JSON.parse(attributes) if attributes.is_a?(String)
540
+ # A single model is one record — reject an Array with a clear message.
541
+ if attributes.is_a?(Array)
542
+ raise ArgumentError,
543
+ "#{self.class}.new expects a Hash, keyword args, or a JSON object string " \
544
+ "for one record — got an Array. Map over the list to build many records."
545
+ end
520
546
  attributes.each do |key, value|
521
547
  setter = "#{key}="
522
548
  __send__(setter, value) if respond_to?(setter)
@@ -80,6 +80,7 @@ module Tina4
80
80
  # Matches Python __call__ / PHP __invoke / Node response() pattern.
81
81
  def call(data = nil, status_code = 200, content_type = nil)
82
82
  @status_code = status_code
83
+ data = jsonable(data)
83
84
  if content_type
84
85
  @headers["content-type"] = content_type
85
86
  @body = data.to_s
@@ -96,10 +97,29 @@ module Tina4
96
97
  def json(data, status_or_opts = nil, status: nil)
97
98
  @status_code = status || (status_or_opts.is_a?(Integer) ? status_or_opts : 200)
98
99
  @headers["content-type"] = JSON_CONTENT_TYPE
100
+ data = jsonable(data)
99
101
  @body = data.is_a?(String) ? data : JSON.generate(data)
100
102
  self
101
103
  end
102
104
 
105
+ # Normalise domain objects into JSON-serialisable structures so handlers can
106
+ # `response.(model)` / `response.json(model)` without calling .to_h by hand:
107
+ #
108
+ # response.json(user) # ORM model -> Hash
109
+ # response.json(User.all) # Array<ORM> -> Array<Hash>
110
+ # response.json(db.fetch(sql)) # DatabaseResult -> Array<Hash>
111
+ #
112
+ # Plain Hash / Array / String pass through unchanged (Array members that are
113
+ # models are still converted).
114
+ def jsonable(data)
115
+ return data.to_h if data.is_a?(Tina4::ORM)
116
+ return data.records if data.is_a?(Tina4::DatabaseResult)
117
+ return data.map { |item| item.is_a?(Tina4::ORM) ? item.to_h : item } if data.is_a?(Array)
118
+
119
+ data
120
+ end
121
+ private :jsonable
122
+
103
123
  def html(content, status_or_opts = nil, status: nil)
104
124
  @status_code = status || (status_or_opts.is_a?(Integer) ? status_or_opts : 200)
105
125
  @headers["content-type"] = HTML_CONTENT_TYPE
data/lib/tina4/seeder.rb CHANGED
@@ -373,7 +373,7 @@ module Tina4
373
373
 
374
374
  db = Tina4.database
375
375
  unless db
376
- Tina4::Log.error("Seeder: No database connection. Set Tina4.database first.")
376
+ Tina4::Log.error("Seeder: No database connection. Call Tina4.bind_database(db) first.")
377
377
  return 0
378
378
  end
379
379
 
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.18"
4
+ VERSION = "3.13.19"
5
5
  end
data/lib/tina4.rb CHANGED
@@ -120,7 +120,26 @@ module Tina4
120
120
  BANNER
121
121
 
122
122
  class << self
123
- attr_accessor :root_dir, :database
123
+ attr_accessor :root_dir
124
+ attr_reader :database
125
+
126
+ # Bind a database connection.
127
+ # bind_database(db) → sets the global default (Tina4.database)
128
+ # bind_database(db, name: :analytics) → registers a named connection
129
+ # A model with `self.db = :analytics` resolves from this named registry;
130
+ # otherwise models fall back to the global default / TINA4_DATABASE_URL.
131
+ def bind_database(db, name: nil)
132
+ if name.nil?
133
+ @database = db
134
+ else
135
+ (@databases ||= {})[name.to_sym] = db
136
+ end
137
+ db
138
+ end
139
+
140
+ # Named connection registry. bind_database(db, name:) populates it;
141
+ # models with a Symbol/String `self.db` resolve against it.
142
+ def databases = (@databases ||= {})
124
143
 
125
144
  def print_banner(host: "0.0.0.0", port: 7147, server_name: nil)
126
145
  # TINA4_SUPPRESS — short-circuit ALL banner output for headless / CI runs.
@@ -450,7 +469,7 @@ module Tina4
450
469
  db_url = ENV["TINA4_DATABASE_URL"]
451
470
  if db_url && !db_url.empty?
452
471
  begin
453
- @database = Tina4::Database.new(db_url)
472
+ bind_database(Tina4::Database.new(db_url))
454
473
  Tina4::Log.info("Database connected: #{db_url.sub(/:[^:@]+@/, ':***@')}")
455
474
  rescue => e
456
475
  Tina4::Log.error("Database connection failed: #{e.message}")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.18
4
+ version: 3.13.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team