CableReady::Updatable: automatically update the DOM when state changes (aka updates_for)#145
Conversation
|
Awesome to see this coming together. 💯 |
Co-authored-by: Erlingur Þorsteinsson <[email protected]>
* Add broadcast_class option to enable_broadcasts * Raise ArgumentError for missing inverse_of at runtime instead of load If there is some reason why the model class for the has_many assocation fails to load (like a typo in the code) it will first fail when reflecting and this ArgumentError will be raised even though it's not the actual problem. Co-authored-by: Erlingur Þorsteinsson <[email protected]>
* Allow compoundable to work with any GlobalId-able entity * Make "marker callbacks" lambdas * Extract ModelBroadcasterCallbacks class * Extract CollectionBroadcasterCallbacks class * Call ModelBroadcasterCallbacks explicitly * Standardize * Align channels[] with compoundable implementation * Rename cable_ready_broadcast * Rename and simplify collections * Extract CollectionsRegistry and bestow it with managing of and broadcasting to collections * Make method private
* Add optional belongs_to test * Fix optional belongs_to
hopsoft
left a comment
There was a problem hiding this comment.
This is slick. A big thanks to all contributors thus far. Nice work!
I did have one question/proposal regarding the public API surface area prior to final approval.
Also, one thing I want to emphasize in the docs is a strong warning regarding extensive use of ActiveRecord callbacks. As noted in the PR description... while extremely powerful, callbacks can prove to be a foot gun if not used thoughtfully.
|
Yeah agreed. We should point out how to use |
The conversation has been started. I'll let the team and contributors take it from here.
|
@leastbad you describe like But the correct isn't? |
|
They are both correct, because they both demonstrate different concepts. As I explained on Discord, the goal of the example was to demonstrate that it works fine if you have multiple content areas with the same identifier. |
CableReady::Updatable: automatically update the DOM when state changes
CableReady::Updatable: automatically update the DOM when state changesCableReady::Updatable: automatically update the DOM when state changes (aka updates_for)
After being really impressed with @andrewculver's exciting Sprinkles video, we decided to see how we could build similar functionality into CableReady. It directly addresses a real blind-spot in the library, which is the case when you need to update the same view for a lot of people with content customized for each person.
The big idea of this feature is that instead of rendering unique content and sending it down the wire, each subscriber gets a ping notification to
fetchthe current page and morphdom it into the content of the web component. This allows ActionController to do what it does best, which is show the user exactly what they are allowed to see. It's also likely to take advantage of HTTP caching.The approach taken in this PR builds on the work already done for the
stream_fromhelper, borrowing its secure identifier infrastructure and following the same general structure.This solution has three significant elements. On the server, there is the
CableReady::Updatable(updatable.rb) which ideally gets included inApplicationRecord. There's also theupdates_forview helper (cable_ready_helper.rb) and the web component it emits,updates-for(updates_for_element.js).While the naming has changed, the functionality is 1:1 with the original video, with a few upgrades:
urlattribute to instuct the web component to pull updates from a different pagefetchcall, so long as parameters are identicalonallows a Symbol or Array of[:create, :update, :destroy]andifaccepts a Symbol or Prochas_manyassociations now support aenable_updatesoption, with the simplest valuetrueindicating "all commits, all of the time" but customizable via Array, Symbol, Hash or Prochas_many :throughassociations are working greatWhen planning your Updates architecture, keep in mind that there are three different possible outcomes:
Let's build up an example:
View code I use to test
enable_updatesmethod (aka updating from model) is:This would pick up any
updatecallback sent from theUsermodel. I use two because it's important to see that there is only onefetchfor two updates.We can add a third instance of the helper, but instead of a model instance, we can pass a class constant to receive
createanddestroycallbacks. This is perfect for anindexview, as it allows us to update a list of instances without getting stuck in "helper for model instance that doesn't exist yet or has just been destroyed" sinkholes:Here's the view code to test association Updates. It's the same, except we pass a Symbol representing the name of the model association in addition to the model instance which owns the collection:
Right now, things are in good shape but there are a few ways to jump in and improve this PR:
tests are sorely needed and highly appreciated✔️is there a way to do✔️enable_updatesbetter; I have concerns that on a busy site or with a big table, running this code for every record could get bad, fastis there a way to move some or all of this PR out of✔️updatable.rbwithout blowing up all of theselfaccessors? even just figuring out how to movemodule ClassMethodsout would make this so much lighterhas_manyifProcs can receive a reference to the current associated model and not just the owner/originator of the association, that'd be great eg. right now if "User has_many Post" the Procs for the posts association receive a reference to the user, but that's likely only half of the story for most Procs you'd write here@hopsoft would love to hear feedback on naming of methods and everything✔️Procs passed to
ifoptions on theenable_broadcastsmethod can access the current model instance asself, so you can do things likeenable_updates on: :create, if: -> { id.odd? }.Remember, if you're testing this in your app and you change one of the
if/onoptions in your app's model, you must refresh the browser to allow ActionCable to reconnect!This solution requires that CableReady be initialized with the memoized consumer in your application pack or controller index. It needs to include something like:
Thanks again to @andrewculver @erlingur @ParamagicDev @julianrubisch @fractaledmind and others who have contributed so far. Great team effort.
html_optionsBroadcast from html options leastbad/cable_ready#11data-cable-permanentordata-morph-permanent? could be done by passing a custompermanentAttributeName: https://github.com/stimulusreflex/cable_ready/blob/master/javascript/morph_callbacks.js#L33