tina4ruby 3.13.158 → 3.13.159

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: 9821daa34797e73cbeb2363f1df52e61a22a17eb2b7e44412483c72a02176866
4
- data.tar.gz: 4eb81de452060f402677cecb522bd11417c968c50940a39cfbf7f9cd42b5700a
3
+ metadata.gz: c8eaa3629bc154d2dfc5419385857a0f9c3a8ede768af9363664c80448be9e19
4
+ data.tar.gz: aefe567a6fb3cf128750add20e11dc7683325a14b978708c57010ac5d4062bbe
5
5
  SHA512:
6
- metadata.gz: b3350a878be47194f468507e910d6975c347eb717d101aeb7d78c4cec836156ab3ee06c234ec63b1c7a84cb8b8e5fcc3d11f226d688813360f7102fe55a61abb
7
- data.tar.gz: f065e3b6de1441f44bd6104f6b9eb2e46c5c5fb856043ec9e86abf7b6a738305130259cf6697239d54a9cd390a97ca77775466e0476c8272d863d672acafb15b
6
+ metadata.gz: 2041d862f935b2a6611323856b8cfc7ed376009f1a0d7a52583f9ada47eeb4da1298e01c8ef6b48f17bbc71bd2d2c13bf4ae712d1004ba80636c772e18ab358f
7
+ data.tar.gz: 1d4d7726c126abab28b2f6261b1f5961249e8a2a8ca77e579941379c7943705d91094b0e2caf7701193a1f633ce7ccdd59a5287b1e1bc111ee438c70d3301cda
data/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@ number means the same thing everywhere.
6
6
  **The authoritative release notes for every shipped version live in the documentation:**
7
7
  https://tina4.com/ruby/36-releases
8
8
 
9
+ ## 3.13.159 - 2026-11-06
10
+
11
+ ### Fixed
12
+ - `generate route/service/queue/websocket/listener` co-emitted a spec that asserted the
13
+ stub still raised `NotImplementedError`, so filling the handler in correctly turned the
14
+ project's own suite red. The generated spec now asserts the handler is wired and stays
15
+ green both as the scaffold stub and once implemented (#270).
16
+ - `tina4ruby migrate` applied a freshly scaffolded comment-only migration and recorded it
17
+ passed, so the real SQL written later under the same filename never ran. The runner now
18
+ keeps a migration pending while it still bears a `tina4:edit` marker and has no
19
+ executable SQL; a scaffolded `CREATE TABLE` is real DDL and still applies (#288).
20
+ - `migrate:create create_*_view` scaffolded a runnable `CREATE TABLE <name>_view`, so
21
+ `migrate` built a table and never the view. A `*_view` name now scaffolds commented
22
+ `CREATE VIEW` guidance that is kept pending until written (#297).
23
+
9
24
  ## 3.13.158 - 2026-11-05
10
25
 
11
26
  ### Added
data/lib/tina4/cli.rb CHANGED
@@ -2446,10 +2446,23 @@ module Tina4
2446
2446
  fields = fields_override&.any? ? fields_override : parse_fields(flags["fields"])
2447
2447
  is_create = name.start_with?("create_") || !fields_override.nil?
2448
2448
 
2449
+ # A `create_*_view` name is a VIEW, not a table. Emitting a runnable
2450
+ # `CREATE TABLE <name>_view` made migrate build a table and never the
2451
+ # view, silently (#297). A view needs a SELECT, so scaffold it as
2452
+ # commented guidance with tina4:edit markers and no executable SQL, so
2453
+ # startup auto-migrate keeps it pending until it is written (#288).
2454
+ is_view = is_create && fields_override.nil? && table.end_with?("_view")
2455
+
2449
2456
  filename = "#{timestamp}_#{name}.sql"
2450
2457
  path = File.join(dir, filename)
2451
2458
 
2452
- if is_create
2459
+ if is_view
2460
+ up_sql = "-- tina4:edit write the CREATE VIEW statement, then remove the\n" \
2461
+ "-- tina4:edit lines. For example:\n" \
2462
+ "-- CREATE VIEW #{table} AS\n" \
2463
+ "-- SELECT id, /* columns */ FROM source_table WHERE /* ... */;"
2464
+ down_sql = "DROP VIEW IF EXISTS #{table};"
2465
+ elsif is_create
2453
2466
  col_lines = [" id INTEGER PRIMARY KEY AUTOINCREMENT"]
2454
2467
  fields.each do |fname, ftype|
2455
2468
  info = FIELD_TYPE_MAP[ftype] || FIELD_TYPE_MAP["string"]
@@ -2500,9 +2513,10 @@ module Tina4
2500
2513
  File.write(down_path, down_content)
2501
2514
  puts " Created #{down_path}"
2502
2515
 
2503
- # Co-emit a real apply-UP/DOWN spec — only for CREATE migrations, whose UP
2504
- # SQL is real DDL to assert against (a placeholder ALTER has nothing yet).
2505
- emit_migration_test(name, table, filename, down_filename) if emit_test && is_create
2516
+ # Co-emit a real apply-UP/DOWN spec — only for CREATE TABLE migrations,
2517
+ # whose UP SQL is real DDL to assert against (a placeholder ALTER, or a
2518
+ # VIEW stub that still needs its SELECT, has nothing concrete yet).
2519
+ emit_migration_test(name, table, filename, down_filename) if emit_test && is_create && !is_view
2506
2520
  end
2507
2521
 
2508
2522
  # ── Generator: middleware ────────────────────────────────────────────
@@ -3492,10 +3506,11 @@ module Tina4
3492
3506
  # Routing spec for #{route} — no mocks, real Router.
3493
3507
  #
3494
3508
  # Generated with src/routes/#{route}.rb by `tina4ruby generate route
3495
- # #{route}` (no --model). The handlers are AI-FILL stubs that raise until
3496
- # you implement them, so this tests what IS live on generation: all five
3497
- # routes register on the REAL Router, and the list handler fails loud until
3498
- # filled. Fill a handler, then assert its real response here.
3509
+ # #{route}` (no --model). The handlers start as AI-FILL stubs that raise
3510
+ # until you implement them. This tests what is live on generation: all
3511
+ # five routes register on the REAL Router, and the list handler is wired
3512
+ # and callable. It stays GREEN whether the stub still raises OR you have
3513
+ # filled it in — implementing the handler never turns this spec red.
3499
3514
  require "spec_helper"
3500
3515
 
3501
3516
  RSpec.describe "#{klass} routing (real Router)" do
@@ -3510,9 +3525,20 @@ module Tina4
3510
3525
  expect(paths).to include(["DELETE", "/api/#{route}/{id:int}"])
3511
3526
  end
3512
3527
 
3513
- it "the list handler is a live stub (raises until filled)" do
3528
+ it "the list handler is wired (green as a stub AND when filled)" do
3529
+ # It starts as a loud AI-FILL stub (raises NotImplementedError); once
3530
+ # you implement it this spec STILL passes — it asserts wiring, never
3531
+ # that the stub is unfinished, so doing the work never turns red.
3514
3532
  route, = Tina4::Router.match("GET", "/api/#{route}")
3515
- expect { route.handler.call(nil, nil) }.to raise_error(NotImplementedError)
3533
+ expect(route.handler).to respond_to(:call)
3534
+ request = Tina4::Request.new("REQUEST_METHOD" => "GET",
3535
+ "PATH_INFO" => "/api/#{route}", "QUERY_STRING" => "")
3536
+ begin
3537
+ result = route.handler.call(request, Tina4::Response.new)
3538
+ rescue NotImplementedError
3539
+ next # still the scaffold stub — expected until filled in
3540
+ end
3541
+ expect(result).not_to be_nil # filled in: must hand back a response
3516
3542
  end
3517
3543
  end
3518
3544
  RUBY
@@ -3581,8 +3607,15 @@ module Tina4
3581
3607
  expect(Tina4::ServiceRunner.list.any? { |s| s[:name] == "#{snake}" }).to be true
3582
3608
  end
3583
3609
 
3584
- it "the task is a live stub (raises until filled)" do
3585
- expect { #{snake}_task(nil) }.to raise_error(NotImplementedError)
3610
+ it "the task is wired (green as a stub AND when filled)" do
3611
+ # Starts as a loud AI-FILL stub (raises NotImplementedError); once
3612
+ # implemented this spec STILL passes, so doing the work never turns red.
3613
+ expect(defined?(#{snake}_task)).to be_truthy
3614
+ begin
3615
+ #{snake}_task(nil)
3616
+ rescue NotImplementedError
3617
+ next # still the scaffold stub — expected until filled in
3618
+ end
3586
3619
  end
3587
3620
  end
3588
3621
  RUBY
@@ -3619,8 +3652,15 @@ module Tina4
3619
3652
  expect(svc[:options][:daemon]).to be true
3620
3653
  end
3621
3654
 
3622
- it "handle_#{slug} is a live stub (raises until filled)" do
3623
- expect { handle_#{slug}({}) }.to raise_error(NotImplementedError)
3655
+ it "handle_#{slug} is wired (green as a stub AND when filled)" do
3656
+ # Starts as a loud AI-FILL stub (raises NotImplementedError); once
3657
+ # implemented this spec STILL passes, so doing the work never turns red.
3658
+ expect(defined?(handle_#{slug})).to be_truthy
3659
+ begin
3660
+ handle_#{slug}({})
3661
+ rescue NotImplementedError
3662
+ next # still the scaffold stub — expected until filled in
3663
+ end
3624
3664
  end
3625
3665
  end
3626
3666
  RUBY
@@ -3727,8 +3767,15 @@ module Tina4
3727
3767
  expect(ws_route.handler.call(nil, :close, nil)).to be_nil
3728
3768
  end
3729
3769
 
3730
- it "the :message branch is a live stub (raises until filled)" do
3731
- expect { ws_route.handler.call(nil, :message, "hi") }.to raise_error(NotImplementedError)
3770
+ it "the :message branch is wired (green as a stub AND when filled)" do
3771
+ # Starts as a loud AI-FILL stub (raises NotImplementedError); once
3772
+ # implemented this spec STILL passes, so doing the work never turns red.
3773
+ expect(ws_route.handler).to respond_to(:call)
3774
+ begin
3775
+ ws_route.handler.call(nil, :message, "hi")
3776
+ rescue NotImplementedError
3777
+ next # still the scaffold stub — expected until filled in
3778
+ end
3732
3779
  end
3733
3780
  end
3734
3781
  RUBY
@@ -3759,8 +3806,16 @@ module Tina4
3759
3806
  expect(Tina4::Events.listeners("#{event}").length).to be >= 1
3760
3807
  end
3761
3808
 
3762
- it "emitting reaches the listener (strict re-raises the stub)" do
3763
- expect { Tina4::Events.emit("#{event}", { "id" => 1 }, strict: true) }.to raise_error(NotImplementedError)
3809
+ it "emitting reaches the listener (green as a stub AND when filled)" do
3810
+ # Starts as a loud AI-FILL stub (strict emit re-raises
3811
+ # NotImplementedError); once implemented this spec STILL passes, so
3812
+ # doing the work never turns red.
3813
+ expect(Tina4::Events.listeners("#{event}").length).to be >= 1
3814
+ begin
3815
+ Tina4::Events.emit("#{event}", { "id" => 1 }, strict: true)
3816
+ rescue NotImplementedError
3817
+ next # still the scaffold stub — expected until filled in
3818
+ end
3764
3819
  end
3765
3820
  end
3766
3821
  RUBY
@@ -199,6 +199,23 @@ module Tina4
199
199
  batch = next_batch_number
200
200
  results = []
201
201
  pending.each do |file|
202
+ # An empty, unedited scaffold migration — one that still bears a
203
+ # tina4:edit marker AND has no executable statement (the comment-only
204
+ # ALTER stub from a non-create migrate:create, or the view stub) — is
205
+ # unfinished. Applying it (and recording it passed) runs nothing and
206
+ # leaves the real SQL, written later under the same filename, never
207
+ # applied, with migrate reporting nothing pending (#288, #297). Keep it
208
+ # pending until written. A scaffolded CREATE TABLE carries the same hint
209
+ # but IS real DDL, so it applies — generate-model -> migrate is untouched.
210
+ if unapplied_scaffold?(file)
211
+ Tina4::Log.warning(
212
+ "Migration #{File.basename(file)} is still an unedited scaffold " \
213
+ "(a 'tina4:edit' placeholder with no SQL yet) — skipped and kept " \
214
+ "pending. Write the migration and remove the tina4:edit line(s), " \
215
+ "then run migrate again."
216
+ )
217
+ next
218
+ end
202
219
  result = run_migration(file, batch)
203
220
  results << result
204
221
  # Stop on failure
@@ -207,6 +224,24 @@ module Tina4
207
224
  results
208
225
  end
209
226
 
227
+ # True if a migration file is still the unedited scaffold placeholder: it
228
+ # bears a tina4:edit marker AND (for a .sql file) has no executable
229
+ # statement. A .rb scaffold qualifies on the marker alone.
230
+ def unapplied_scaffold?(file)
231
+ raw = File.read(file)
232
+ return false unless raw =~ %r{(?:#|//|--|/\*|\{#)\s*tina4:edit\b}i
233
+ return true if file.end_with?(".rb")
234
+ !executable_sql?(raw)
235
+ rescue SystemCallError
236
+ false
237
+ end
238
+
239
+ # True if any executable statement remains once SQL comments are stripped.
240
+ def executable_sql?(sql)
241
+ body = sql.gsub(%r{/\*.*?\*/}m, "").gsub(/--[^\n]*/, "")
242
+ !body.gsub(/[;\s]/, "").empty?
243
+ end
244
+
210
245
  # A stable signed-64-bit key for PostgreSQL's pg_advisory_lock, derived from
211
246
  # the lock name so it cannot collide with an application's own advisory-lock
212
247
  # keys. It is a constant (not user input), so inlining it in the SQL is
data/lib/tina4/version.rb CHANGED
@@ -7,5 +7,5 @@
7
7
 
8
8
 
9
9
  module Tina4
10
- VERSION = "3.13.158"
10
+ VERSION = "3.13.159"
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.158
4
+ version: 3.13.159
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-11-05 00:00:00.000000000 Z
11
+ date: 2026-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3