Skip to content

Add Noticed compiler#22

Open
marcus-deans wants to merge 15 commits intoangellist:mainfrom
marcus-deans:add-noticed-compiler
Open

Add Noticed compiler#22
marcus-deans wants to merge 15 commits intoangellist:mainfrom
marcus-deans:add-noticed-compiler

Conversation

@marcus-deans
Copy link
Contributor

@marcus-deans marcus-deans commented Jan 24, 2026

Summary

Adds a DSL compiler for the Noticed gem that generates RBI files for Noticed::Event and Noticed::Ephemeral subclasses.

Problem

The base Noticed gem RBI (from tapioca gem noticed) provides generic return types for class methods. This causes issues:
1. The with method returns a generic type instead of the specific notifier instance type
2. Tapioca's ActiveRecord compiler generates a with method for CTEs that can shadow Noticed's with method

Solution

This compiler generates proper class method signatures for with, deliver, and deliver_later on notifier subclasses with accurate return types:

     class NewCommentNotifier
       class << self
         sig { params(params: T::Hash[Symbol, T.untyped]).returns(::NewCommentNotifier) }
         def with(params); end

         sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::NewCommentNotifier) }
         def deliver(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end

         sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::NewCommentNotifier) }
         def deliver_later(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end
       end
     end

Why Instance Return Types Are Correct

The Noticed gem's class methods return instances:

  • with(params) calls new(params: params) → returns instance
  • deliver(...) calls new.deliver(...) → returns instance
  • deliver_later is an alias of deliver

Method chaining works because MyNotifier.with(params).deliver(user) calls:
1. Class method with → returns instance
2. Instance method deliver on that instance → returns self

Changes

  • lib/tapioca/dsl/compilers/noticed.rb - New compiler
  • spec/tapioca/dsl/compilers/noticed_spec.rb - Tests
  • manual/compiler_noticed.md - Documentation
  • manual/compilers.md - Added to compiler list
  • Gemfile - Added noticed gem (require: false) for testing
    • sorbet/tapioca/config.yml - Excluded noticed from gem RBI generation

Copy link
Member

@stathis-alexander stathis-alexander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thank you for the contribution @marcus-deans !

We don't use noticed at AL, but it seems like a nice gem that I may look into using for Slack in the future.

Is there anything else to add here or good to ship?

Comment on lines 69 to 87
singleton.create_method(
'deliver',
parameters: [
create_opt_param('recipients', type: 'T.untyped', default: 'T.unsafe(nil)'),
create_kw_opt_param('enqueue_job', type: 'T.nilable(T::Boolean)', default: 'T.unsafe(nil)'),
create_kw_rest_param('options', type: 'T.untyped')
],
return_type: 'void'
)

singleton.create_method(
'deliver_later',
parameters: [
create_opt_param('recipients', type: 'T.untyped', default: 'T.unsafe(nil)'),
create_kw_opt_param('enqueue_job', type: 'T.nilable(T::Boolean)', default: 'T.unsafe(nil)'),
create_kw_rest_param('options', type: 'T.untyped')
],
return_type: 'void'
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking, but can we use reflection to make any of these types more precise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upgraded to within possible options and appropriate class type - but recipients can basically be anything. very adhoc Railsy approach to things that doesn't translate well to any sort of structured programming

@marcus-deans
Copy link
Contributor Author

Urgh - see CI failure now. We use something like this in our Tapioca prerequire - gem implementation is annoying and introspects the DB

require 'active_record'
require 'active_record/connection_adapters/deduplicable'
require 'activerecord-nulldb-adapter'

# Establish a dummy database connection
ActiveRecord::Base.establish_connection(adapter: 'nulldb')

Of course, want to avoid doing that here. Will do some thinking on it and see if can come up with a better solution

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@stathis-alexander
Copy link
Member

@marcus-deans check out how we do specs for the active record compilers we have. Copied from tapioca. Works well.

@marcus-deans
Copy link
Contributor Author

@stathis-alexander got CI green but had to exclude noticed from RBI generation. specifically, it has a reliance on a Rails Engine, which I documented in the specs as well to rationalize the stubs

that being said, that makes this compiler a fair amount less clean than some of the others. don't necessarily have great ideas on how to fix (as mentioned, do a bit of a hack on our local setup too). happy to just close this out if it's not a good fit overall

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants