Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize and sign operations, then enqueue them #100

Conversation

julianrubisch
Copy link
Contributor

Enhancement

This PR allows the safe (de)serialization of operations to allow for the passing of such a verified_operations string to a CableReady::Channel instance. This is useful especially for plugins depending on CableReady to allow for more flexibility.

I've also added minimal YARD docs as a starter.

Rationale and Example

Working on https://github.com/julianrubisch/cable_ready_callbacks, I realized I needed to somehow pass operations as arguments to an ActiveJob, since (of course) serializing a block doesn't work.

So an exemplary use would look like this:

  # model
  after_save_commit CableReadyCallbacks::CableReadyMorph.new([{morph: {selector: "h3", html: "<h3>YES</h3>"}}])

  # callback
  class CableReadyMorph
    def initialize(operations = nil)
      @operations = CableReady::OperationsSerializer.new(*operations) unless operations.nil?
    end

    def after_commit(resource)
      CableReadyCallbacks::StreamResourceJob.perform_later resource, @operations&.verified
    end
  end

  # job
   cable_ready[channel_name].apply(operations).broadcast_to(resource)

Implementation

This PR introduces a new convenience module to obtain an ActiveSupport::MessageVerifier (more or less copied from futurism) and a CableReady::OperationsSerializer class to spit out the verified string.

The meat is a new apply method in Channel, essentially deserializing and enqueueing the operations passed to it.

@hopsoft
Copy link
Contributor

hopsoft commented Jan 19, 2021

I really like the idea of providing a way for CableReady::Channel to serialize the enqueued set of operations that could be used to rehydrate a channel with operations in another process. It could facilitate a broadcast_later method among other things.

@hopsoft
Copy link
Contributor

hopsoft commented Jan 19, 2021

I'm imagining an interface on CR itself for marshaling/serializing enqueued operations. Something like this perhaps.

cable_ready[...]
  .dispatch_event(...)
  .outer_html(...)
  .set_attribute(...)
  .marshal(clear: true)

@julianrubisch
Copy link
Contributor Author

Yeah the thing is I can’t expose that to a job

I want the caller of the job to be able to provide a sequence of operations

@leastbad
Copy link
Contributor

leastbad commented Feb 7, 2021

I've spent the last few hours working with this PR, in the context of the cable_ready_callbacks gem and the stimulus-reflex-patterns project Julian branched to prepare a working example. I really like where this is headed.

I want to get this out of the way: last night I was inspired by a conversation with @paramagicdev and built #108 without any notion that I was potentially reinventing something you'd been working on. Hopefully if you check out that PR, you'll see that I was working towards an entirely different end, which was to provide an agnostic interface with which devs could build CR client compatible JSON from enqueued operations. I figured that sooner than later, we'd need CR to work whether ActionCable was in the picture or not... hence, cable_car!

Now that I'm fully appraised of Julian's serializable-operations, my major takeaways:

  1. Totally stealing that apply concept for cable_car aka 'Ajax mode' #108
  2. I tend to agree with @marcoroth that the MessageVerifier stuff isn't super helpful in this context; I just don't see this being an attack vector (sorry! ❤️ )
  3. Therefore, I think we should proceed with cable_car aka 'Ajax mode' #108 instead of Serialize and sign operations, then enqueue them #100, and modify cable_ready_callbacks to take a Hash
  4. The apply method should stay! In cable_car aka 'Ajax mode' #108, I introduce an Applicable module that gets included in both cable_car and channel. (It also contains a to_json override.)

The primary justification is that the syntax implied by #100 is effectively a "similar but different" syntax. The developer is building a hash instead of adding operations:

after_save_commit CableReadyCallbacks::CableReadyMorph.new([{morph: {selector: "h3", html: "<h3>JUST GOT UPDATED</h3>"}}])

Instead, we can make it more like using the existing cable_ready API style:

after_save_commit CableReadyCallbacks::CableReadyMorph.new cable_car.morph(selector: "h3", html: "<h3>JUST GOT UPDATED</h3>")

I'd also love to see the addition of a convenience helper so that CableReadyCallbacks::CableReadyMorph.new feels a bit more like Turbo's broadcast_replace_later. I don't want our API to be as good as theirs, I want it to be demonstrably better/shorter.

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.

3 participants