cable_ready 5.0.0.pre0 → 5.0.0.pre4
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 +4 -4
 - data/CHANGELOG.md +87 -8
 - data/Gemfile.lock +105 -97
 - data/README.md +10 -6
 - data/app/channels/cable_ready/stream.rb +12 -0
 - data/app/helpers/cable_ready_helper.rb +24 -0
 - data/app/jobs/cable_ready_broadcast_job.rb +14 -0
 - data/app/models/concerns/cable_ready/updatable/collection_updatable_callbacks.rb +19 -0
 - data/app/models/concerns/cable_ready/updatable/collections_registry.rb +33 -0
 - data/app/models/concerns/cable_ready/updatable/model_updatable_callbacks.rb +28 -0
 - data/app/models/concerns/cable_ready/updatable.rb +98 -0
 - data/app/models/concerns/extend_has_many.rb +13 -0
 - data/lib/cable_ready/channels.rb +1 -1
 - data/lib/cable_ready/compoundable.rb +1 -1
 - data/lib/cable_ready/config.rb +2 -0
 - data/lib/cable_ready/identifiable.rb +13 -2
 - data/lib/cable_ready/operation_builder.rb +25 -14
 - data/lib/cable_ready/sanity_checker.rb +50 -50
 - data/lib/cable_ready/version.rb +1 -1
 - data/lib/cable_ready.rb +3 -0
 - data/lib/generators/cable_ready/{stream_from_generator.rb → helpers_generator.rb} +1 -1
 - data/test/dummy/app/channels/application_cable/channel.rb +4 -0
 - data/test/dummy/app/channels/application_cable/connection.rb +4 -0
 - data/test/dummy/app/controllers/application_controller.rb +2 -0
 - data/test/dummy/app/helpers/application_helper.rb +2 -0
 - data/test/dummy/app/jobs/application_job.rb +7 -0
 - data/test/dummy/app/mailers/application_mailer.rb +4 -0
 - data/test/dummy/app/models/application_record.rb +3 -0
 - data/test/dummy/app/models/global_idable_entity.rb +16 -0
 - data/test/dummy/app/models/post.rb +4 -0
 - data/test/dummy/app/models/section.rb +6 -0
 - data/test/dummy/app/models/team.rb +6 -0
 - data/test/dummy/app/models/topic.rb +4 -0
 - data/test/dummy/app/models/user.rb +7 -0
 - data/test/dummy/config/application.rb +22 -0
 - data/test/dummy/config/boot.rb +5 -0
 - data/test/dummy/config/environment.rb +5 -0
 - data/test/dummy/config/environments/development.rb +76 -0
 - data/test/dummy/config/environments/production.rb +120 -0
 - data/test/dummy/config/environments/test.rb +59 -0
 - data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
 - data/test/dummy/config/initializers/assets.rb +12 -0
 - data/test/dummy/config/initializers/backtrace_silencers.rb +8 -0
 - data/test/dummy/config/initializers/cable_ready.rb +18 -0
 - data/test/dummy/config/initializers/content_security_policy.rb +28 -0
 - data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
 - data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
 - data/test/dummy/config/initializers/inflections.rb +16 -0
 - data/test/dummy/config/initializers/mime_types.rb +4 -0
 - data/test/dummy/config/initializers/permissions_policy.rb +11 -0
 - data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
 - data/test/dummy/config/puma.rb +43 -0
 - data/test/dummy/config/routes.rb +3 -0
 - data/test/dummy/db/migrate/20210902154139_create_users.rb +9 -0
 - data/test/dummy/db/migrate/20210902154153_create_posts.rb +10 -0
 - data/test/dummy/db/migrate/20210904081930_create_topics.rb +9 -0
 - data/test/dummy/db/migrate/20210904093607_create_sections.rb +9 -0
 - data/test/dummy/db/migrate/20210913191735_create_teams.rb +8 -0
 - data/test/dummy/db/migrate/20210913191759_add_team_reference_to_users.rb +5 -0
 - data/test/dummy/db/schema.rb +49 -0
 - data/test/dummy/test/models/post_test.rb +7 -0
 - data/test/dummy/test/models/section_test.rb +7 -0
 - data/test/dummy/test/models/team_test.rb +7 -0
 - data/test/dummy/test/models/topic_test.rb +7 -0
 - data/test/dummy/test/models/user_test.rb +7 -0
 - data/test/lib/cable_ready/cable_car_test.rb +25 -3
 - data/test/lib/cable_ready/compoundable_test.rb +26 -0
 - data/test/lib/cable_ready/helper_test.rb +25 -0
 - data/test/lib/cable_ready/identifiable_test.rb +0 -6
 - data/test/lib/cable_ready/operation_builder_test.rb +89 -28
 - data/test/lib/cable_ready/updatable_test.rb +112 -0
 - data/test/test_helper.rb +4 -1
 - metadata +126 -14
 - data/cable_ready.gemspec +0 -27
 - data/package.json +0 -39
 - data/tags +0 -80
 - data/yarn.lock +0 -2562
 
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module CableReady
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Updatable
         
     | 
| 
      
 5 
     | 
    
         
            +
                extend ::ActiveSupport::Concern
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                included do |base|
         
     | 
| 
      
 8 
     | 
    
         
            +
                  if base < ActiveRecord::Base
         
     | 
| 
      
 9 
     | 
    
         
            +
                    include ExtendHasMany
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    after_commit CollectionUpdatableCallbacks.new(:create), on: :create
         
     | 
| 
      
 12 
     | 
    
         
            +
                    after_commit CollectionUpdatableCallbacks.new(:update), on: :update
         
     | 
| 
      
 13 
     | 
    
         
            +
                    after_commit CollectionUpdatableCallbacks.new(:destroy), on: :destroy
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    def self.enable_updates(*options)
         
     | 
| 
      
 16 
     | 
    
         
            +
                      options = options.extract_options!
         
     | 
| 
      
 17 
     | 
    
         
            +
                      options = {
         
     | 
| 
      
 18 
     | 
    
         
            +
                        on: [:create, :update, :destroy],
         
     | 
| 
      
 19 
     | 
    
         
            +
                        if: -> { true }
         
     | 
| 
      
 20 
     | 
    
         
            +
                      }.merge(options)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                      enabled_operations = Array(options[:on])
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                      after_commit(ModelUpdatableCallbacks.new(:create, enabled_operations), {on: :create, if: options[:if]})
         
     | 
| 
      
 25 
     | 
    
         
            +
                      after_commit(ModelUpdatableCallbacks.new(:update, enabled_operations), {on: :update, if: options[:if]})
         
     | 
| 
      
 26 
     | 
    
         
            +
                      after_commit(ModelUpdatableCallbacks.new(:destroy, enabled_operations), {on: :destroy, if: options[:if]})
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                private
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 34 
     | 
    
         
            +
                  def has_many(name, scope = nil, **options, &extension)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    option = options.delete(:enable_updates)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    broadcast = option.present?
         
     | 
| 
      
 37 
     | 
    
         
            +
                    result = super
         
     | 
| 
      
 38 
     | 
    
         
            +
                    enrich_association_with_updates(name, option) if broadcast
         
     | 
| 
      
 39 
     | 
    
         
            +
                    result
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  def cable_ready_collections
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @cable_ready_collections ||= CollectionsRegistry.new
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  def cable_ready_update_collection(resource, name)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    identifier = resource.to_global_id.to_s + ":" + name.to_s
         
     | 
| 
      
 48 
     | 
    
         
            +
                    ActionCable.server.broadcast(identifier, {})
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def enrich_association_with_updates(name, option)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    reflection = reflect_on_association(name)
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    inverse_of = reflection.inverse_of&.name&.to_s
         
     | 
| 
      
 55 
     | 
    
         
            +
                    through_association = nil
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    if reflection.through_reflection?
         
     | 
| 
      
 58 
     | 
    
         
            +
                      inverse_of = reflection.through_reflection.inverse_of&.name&.to_s
         
     | 
| 
      
 59 
     | 
    
         
            +
                      through_association = reflection.through_reflection.name.to_s.singularize
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    options = {
         
     | 
| 
      
 63 
     | 
    
         
            +
                      on: [:create, :update, :destroy],
         
     | 
| 
      
 64 
     | 
    
         
            +
                      if: ->(resource) { true }
         
     | 
| 
      
 65 
     | 
    
         
            +
                    }
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                    case option
         
     | 
| 
      
 68 
     | 
    
         
            +
                    when TrueClass
         
     | 
| 
      
 69 
     | 
    
         
            +
                      # proceed!
         
     | 
| 
      
 70 
     | 
    
         
            +
                    when FalseClass
         
     | 
| 
      
 71 
     | 
    
         
            +
                      options[:on] = []
         
     | 
| 
      
 72 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 73 
     | 
    
         
            +
                      options[:on] = option
         
     | 
| 
      
 74 
     | 
    
         
            +
                    when Symbol
         
     | 
| 
      
 75 
     | 
    
         
            +
                      options[:on] = [option]
         
     | 
| 
      
 76 
     | 
    
         
            +
                    when Hash
         
     | 
| 
      
 77 
     | 
    
         
            +
                      option[:on] = Array(option[:on]) if option[:on]
         
     | 
| 
      
 78 
     | 
    
         
            +
                      options = options.merge!(option)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    when Proc
         
     | 
| 
      
 80 
     | 
    
         
            +
                      options[:if] = option
         
     | 
| 
      
 81 
     | 
    
         
            +
                    else
         
     | 
| 
      
 82 
     | 
    
         
            +
                      raise ArgumentError, "Invalid enable_updates option #{option}"
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    reflection.klass.send(:include, CableReady::Updatable) unless reflection.klass.respond_to?(:cable_ready_collections)
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    reflection.klass.cable_ready_collections.register({
         
     | 
| 
      
 88 
     | 
    
         
            +
                      klass: self,
         
     | 
| 
      
 89 
     | 
    
         
            +
                      foreign_key: reflection.foreign_key,
         
     | 
| 
      
 90 
     | 
    
         
            +
                      name: name,
         
     | 
| 
      
 91 
     | 
    
         
            +
                      inverse_association: inverse_of,
         
     | 
| 
      
 92 
     | 
    
         
            +
                      through_association: through_association,
         
     | 
| 
      
 93 
     | 
    
         
            +
                      options: options
         
     | 
| 
      
 94 
     | 
    
         
            +
                    })
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ExtendHasMany
         
     | 
| 
      
 4 
     | 
    
         
            +
              extend ::ActiveSupport::Concern
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              class_methods do
         
     | 
| 
      
 7 
     | 
    
         
            +
                def has_many(*args, &block)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  options = args.extract_options!
         
     | 
| 
      
 9 
     | 
    
         
            +
                  options[:extend] = Array(options[:extend]).push(ClassMethods)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  super(*args, **options, &block)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/cable_ready/channels.rb
    CHANGED
    
    | 
         @@ -13,7 +13,7 @@ module CableReady 
     | 
|
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                def [](*keys)
         
     | 
| 
       15 
15 
     | 
    
         
             
                  keys.select!(&:itself)
         
     | 
| 
       16 
     | 
    
         
            -
                  identifier = keys.many? || (keys.one? && keys.first. 
     | 
| 
      
 16 
     | 
    
         
            +
                  identifier = keys.many? || (keys.one? && keys.first.respond_to?(:to_global_id)) ? compound(keys) : keys.pop
         
     | 
| 
       17 
17 
     | 
    
         
             
                  @channels[identifier] ||= CableReady::Channel.new(identifier)
         
     | 
| 
       18 
18 
     | 
    
         
             
                end
         
     | 
| 
       19 
19 
     | 
    
         | 
    
        data/lib/cable_ready/config.rb
    CHANGED
    
    
| 
         @@ -3,9 +3,13 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module CableReady
         
     | 
| 
       4 
4 
     | 
    
         
             
              module Identifiable
         
     | 
| 
       5 
5 
     | 
    
         
             
                def dom_id(record, prefix = nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  return record.to_dom_selector if record.respond_to?(:to_dom_selector)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
       6 
8 
     | 
    
         
             
                  prefix = prefix.to_s.strip if prefix
         
     | 
| 
       7 
9 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
                  id = if record. 
     | 
| 
      
 10 
     | 
    
         
            +
                  id = if record.respond_to?(:to_dom_id)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    record.to_dom_id
         
     | 
| 
      
 12 
     | 
    
         
            +
                  elsif record.is_a?(ActiveRecord::Relation)
         
     | 
| 
       9 
13 
     | 
    
         
             
                    [prefix, record.model_name.plural].compact.join("_")
         
     | 
| 
       10 
14 
     | 
    
         
             
                  elsif record.is_a?(ActiveRecord::Base)
         
     | 
| 
       11 
15 
     | 
    
         
             
                    ActionView::RecordIdentifier.dom_id(record, prefix)
         
     | 
| 
         @@ -13,7 +17,14 @@ module CableReady 
     | 
|
| 
       13 
17 
     | 
    
         
             
                    [prefix, record.to_s.strip].compact.join("_")
         
     | 
| 
       14 
18 
     | 
    
         
             
                  end
         
     | 
| 
       15 
19 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                  "##{id}".squeeze("#").strip
         
     | 
| 
      
 20 
     | 
    
         
            +
                  "##{id}".squeeze("#").strip.downcase
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def identifiable?(obj)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  obj.respond_to?(:to_dom_selector) ||
         
     | 
| 
      
 25 
     | 
    
         
            +
                    obj.respond_to?(:to_dom_id) ||
         
     | 
| 
      
 26 
     | 
    
         
            +
                    obj.is_a?(ActiveRecord::Relation) ||
         
     | 
| 
      
 27 
     | 
    
         
            +
                    obj.is_a?(ActiveRecord::Base)
         
     | 
| 
       17 
28 
     | 
    
         
             
                end
         
     | 
| 
       18 
29 
     | 
    
         
             
              end
         
     | 
| 
       19 
30 
     | 
    
         
             
            end
         
     | 
| 
         @@ -24,17 +24,32 @@ module CableReady 
     | 
|
| 
       24 
24 
     | 
    
         
             
                def add_operation_method(name)
         
     | 
| 
       25 
25 
     | 
    
         
             
                  return if respond_to?(name)
         
     | 
| 
       26 
26 
     | 
    
         
             
                  singleton_class.public_send :define_method, name, ->(*args) {
         
     | 
| 
       27 
     | 
    
         
            -
                     
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
      
 27 
     | 
    
         
            +
                    if args.one? && args.first.respond_to?(:to_operation_options) && [Array, Hash].include?(args.first.to_operation_options.class)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      case args.first.to_operation_options
         
     | 
| 
      
 29 
     | 
    
         
            +
                      when Array
         
     | 
| 
      
 30 
     | 
    
         
            +
                        selector, options = nil, args.first.to_operation_options
         
     | 
| 
      
 31 
     | 
    
         
            +
                          .select { |e| e.is_a?(Symbol) && args.first.respond_to?("to_#{e}".to_sym) }
         
     | 
| 
      
 32 
     | 
    
         
            +
                          .each_with_object({}) { |option, memo| memo[option.to_s] = args.first.send("to_#{option}".to_sym) }
         
     | 
| 
      
 33 
     | 
    
         
            +
                      when Hash
         
     | 
| 
      
 34 
     | 
    
         
            +
                        selector, options = nil, args.first.to_operation_options
         
     | 
| 
      
 35 
     | 
    
         
            +
                      else
         
     | 
| 
      
 36 
     | 
    
         
            +
                        raise TypeError, ":to_operation_options returned an #{args.first.to_operation_options.class.name}. Must be an Array or Hash."
         
     | 
| 
      
 37 
     | 
    
         
            +
                      end
         
     | 
| 
      
 38 
     | 
    
         
            +
                    else
         
     | 
| 
      
 39 
     | 
    
         
            +
                      selector, options = nil, args.first || {} # 1 or 0 params
         
     | 
| 
      
 40 
     | 
    
         
            +
                      selector, options = options, {} unless options.is_a?(Hash) # swap if only selector provided
         
     | 
| 
      
 41 
     | 
    
         
            +
                      selector, options = args[0, 2] if args.many? # 2 or more params
         
     | 
| 
      
 42 
     | 
    
         
            +
                      options.stringify_keys!
         
     | 
| 
      
 43 
     | 
    
         
            +
                      options.each { |key, value| options[key] = value.send("to_#{key}".to_sym) if value.respond_to?("to_#{key}".to_sym) }
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
       31 
45 
     | 
    
         
             
                    options["selector"] = selector if selector && options.exclude?("selector")
         
     | 
| 
       32 
46 
     | 
    
         
             
                    options["selector"] = previous_selector if previous_selector && options.exclude?("selector")
         
     | 
| 
       33 
47 
     | 
    
         
             
                    if options.include?("selector")
         
     | 
| 
       34 
48 
     | 
    
         
             
                      @previous_selector = options["selector"]
         
     | 
| 
       35 
     | 
    
         
            -
                      options["selector"] =  
     | 
| 
      
 49 
     | 
    
         
            +
                      options["selector"] = identifiable?(previous_selector) ? dom_id(previous_selector) : previous_selector
         
     | 
| 
       36 
50 
     | 
    
         
             
                    end
         
     | 
| 
       37 
     | 
    
         
            -
                     
     | 
| 
      
 51 
     | 
    
         
            +
                    options["operation"] = name.to_s.camelize(:lower)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @enqueued_operations << options
         
     | 
| 
       38 
53 
     | 
    
         
             
                    self
         
     | 
| 
       39 
54 
     | 
    
         
             
                  }
         
     | 
| 
       40 
55 
     | 
    
         
             
                end
         
     | 
| 
         @@ -43,26 +58,22 @@ module CableReady 
     | 
|
| 
       43 
58 
     | 
    
         
             
                  @enqueued_operations.to_json(*args)
         
     | 
| 
       44 
59 
     | 
    
         
             
                end
         
     | 
| 
       45 
60 
     | 
    
         | 
| 
       46 
     | 
    
         
            -
                def apply!(operations = " 
     | 
| 
      
 61 
     | 
    
         
            +
                def apply!(operations = "[]")
         
     | 
| 
       47 
62 
     | 
    
         
             
                  operations = begin
         
     | 
| 
       48 
63 
     | 
    
         
             
                    JSON.parse(operations.is_a?(String) ? operations : operations.to_json)
         
     | 
| 
       49 
64 
     | 
    
         
             
                  rescue JSON::ParserError
         
     | 
| 
       50 
65 
     | 
    
         
             
                    {}
         
     | 
| 
       51 
66 
     | 
    
         
             
                  end
         
     | 
| 
       52 
     | 
    
         
            -
                  operations 
     | 
| 
       53 
     | 
    
         
            -
                    operation.each do |enqueued_operation|
         
     | 
| 
       54 
     | 
    
         
            -
                      @enqueued_operations[name.to_s] << enqueued_operation
         
     | 
| 
       55 
     | 
    
         
            -
                    end
         
     | 
| 
       56 
     | 
    
         
            -
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @enqueued_operations.push(operations)
         
     | 
| 
       57 
68 
     | 
    
         
             
                  self
         
     | 
| 
       58 
69 
     | 
    
         
             
                end
         
     | 
| 
       59 
70 
     | 
    
         | 
| 
       60 
71 
     | 
    
         
             
                def operations_payload
         
     | 
| 
       61 
     | 
    
         
            -
                  @enqueued_operations. 
     | 
| 
      
 72 
     | 
    
         
            +
                  @enqueued_operations.map { |operation| operation.deep_transform_keys! { |key| key.to_s.camelize(:lower) } }
         
     | 
| 
       62 
73 
     | 
    
         
             
                end
         
     | 
| 
       63 
74 
     | 
    
         | 
| 
       64 
75 
     | 
    
         
             
                def reset!
         
     | 
| 
       65 
     | 
    
         
            -
                  @enqueued_operations =  
     | 
| 
      
 76 
     | 
    
         
            +
                  @enqueued_operations = []
         
     | 
| 
       66 
77 
     | 
    
         
             
                  @previous_selector = nil
         
     | 
| 
       67 
78 
     | 
    
         
             
                end
         
     | 
| 
       68 
79 
     | 
    
         
             
              end
         
     | 
| 
         @@ -7,48 +7,59 @@ class CableReady::SanityChecker 
     | 
|
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
              class << self
         
     | 
| 
       9 
9 
     | 
    
         
             
                def check!
         
     | 
| 
      
 10 
     | 
    
         
            +
                  return if ENV["SKIP_SANITY_CHECK"]
         
     | 
| 
       10 
11 
     | 
    
         
             
                  return if CableReady.config.on_failed_sanity_checks == :ignore
         
     | 
| 
       11 
12 
     | 
    
         
             
                  return if called_by_generate_config?
         
     | 
| 
      
 13 
     | 
    
         
            +
                  return if called_by_rake?
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
15 
     | 
    
         
             
                  instance = new
         
     | 
| 
       14 
     | 
    
         
            -
                  instance. 
     | 
| 
      
 16 
     | 
    
         
            +
                  instance.check_package_versions_match
         
     | 
| 
       15 
17 
     | 
    
         
             
                  instance.check_new_version_available
         
     | 
| 
       16 
18 
     | 
    
         
             
                end
         
     | 
| 
       17 
19 
     | 
    
         | 
| 
       18 
20 
     | 
    
         
             
                private
         
     | 
| 
       19 
21 
     | 
    
         | 
| 
       20 
22 
     | 
    
         
             
                def called_by_generate_config?
         
     | 
| 
       21 
     | 
    
         
            -
                  ARGV.include? 
     | 
| 
      
 23 
     | 
    
         
            +
                  ARGV.include?("cable_ready:initializer")
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def called_by_rake?
         
     | 
| 
      
 27 
     | 
    
         
            +
                  File.basename($PROGRAM_NAME) == "rake"
         
     | 
| 
       22 
28 
     | 
    
         
             
                end
         
     | 
| 
       23 
29 
     | 
    
         
             
              end
         
     | 
| 
       24 
30 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
              def  
     | 
| 
       26 
     | 
    
         
            -
                if  
     | 
| 
      
 31 
     | 
    
         
            +
              def check_package_versions_match
         
     | 
| 
      
 32 
     | 
    
         
            +
                if npm_version.nil?
         
     | 
| 
       27 
33 
     | 
    
         
             
                  warn_and_exit <<~WARN
         
     | 
| 
       28 
     | 
    
         
            -
                    Can't locate the cable_ready npm package.
         
     | 
| 
      
 34 
     | 
    
         
            +
                    👉 Can't locate the cable_ready npm package.
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                      yarn add cable_ready@#{gem_version}
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
       29 
38 
     | 
    
         
             
                    Either add it to your package.json as a dependency or use "yarn link cable_ready" if you are doing development.
         
     | 
| 
       30 
39 
     | 
    
         
             
                  WARN
         
     | 
| 
       31 
40 
     | 
    
         
             
                end
         
     | 
| 
       32 
41 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                 
     | 
| 
      
 42 
     | 
    
         
            +
                if package_version_mismatch?
         
     | 
| 
       34 
43 
     | 
    
         
             
                  warn_and_exit <<~WARN
         
     | 
| 
       35 
     | 
    
         
            -
                    The cable_ready npm package version (#{ 
     | 
| 
      
 44 
     | 
    
         
            +
                    👉 The cable_ready npm package version (#{npm_version}) does not match the Rubygem version (#{gem_version}).
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
       36 
46 
     | 
    
         
             
                    To update the cable_ready npm package:
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
       37 
48 
     | 
    
         
             
                      yarn upgrade cable_ready@#{gem_version}
         
     | 
| 
       38 
49 
     | 
    
         
             
                  WARN
         
     | 
| 
       39 
50 
     | 
    
         
             
                end
         
     | 
| 
       40 
51 
     | 
    
         
             
              end
         
     | 
| 
       41 
52 
     | 
    
         | 
| 
       42 
53 
     | 
    
         
             
              def check_new_version_available
         
     | 
| 
       43 
     | 
    
         
            -
                return unless Rails.env.development?
         
     | 
| 
       44 
54 
     | 
    
         
             
                return if CableReady.config.on_new_version_available == :ignore
         
     | 
| 
       45 
     | 
    
         
            -
                return  
     | 
| 
      
 55 
     | 
    
         
            +
                return if Rails.env.development? == false
         
     | 
| 
      
 56 
     | 
    
         
            +
                return if using_preview_release?
         
     | 
| 
       46 
57 
     | 
    
         
             
                begin
         
     | 
| 
       47 
58 
     | 
    
         
             
                  latest_version = URI.open("https://raw.githubusercontent.com/stimulusreflex/cable_ready/master/LATEST", open_timeout: 1, read_timeout: 1).read.strip
         
     | 
| 
       48 
59 
     | 
    
         
             
                  if latest_version != CableReady::VERSION
         
     | 
| 
       49 
60 
     | 
    
         
             
                    puts <<~WARN
         
     | 
| 
       50 
61 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
                      There is a new version of CableReady available!
         
     | 
| 
      
 62 
     | 
    
         
            +
                      👉 There is a new version of CableReady available!
         
     | 
| 
       52 
63 
     | 
    
         
             
                      Current: #{CableReady::VERSION} Latest: #{latest_version}
         
     | 
| 
       53 
64 
     | 
    
         | 
| 
       54 
65 
     | 
    
         
             
                      If you upgrade, it is very important that you update BOTH Gemfile and package.json
         
     | 
| 
         @@ -58,31 +69,31 @@ class CableReady::SanityChecker 
     | 
|
| 
       58 
69 
     | 
    
         
             
                    exit if CableReady.config.on_new_version_available == :exit
         
     | 
| 
       59 
70 
     | 
    
         
             
                  end
         
     | 
| 
       60 
71 
     | 
    
         
             
                rescue
         
     | 
| 
       61 
     | 
    
         
            -
                  puts "CableReady #{CableReady::VERSION} update check skipped: connection timeout"
         
     | 
| 
      
 72 
     | 
    
         
            +
                  puts "👉 CableReady #{CableReady::VERSION} update check skipped: connection timeout"
         
     | 
| 
       62 
73 
     | 
    
         
             
                end
         
     | 
| 
       63 
74 
     | 
    
         
             
              end
         
     | 
| 
       64 
75 
     | 
    
         | 
| 
       65 
76 
     | 
    
         
             
              private
         
     | 
| 
       66 
77 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
              def  
     | 
| 
       68 
     | 
    
         
            -
                 
     | 
| 
      
 78 
     | 
    
         
            +
              def package_version_mismatch?
         
     | 
| 
      
 79 
     | 
    
         
            +
                npm_version != gem_version
         
     | 
| 
       69 
80 
     | 
    
         
             
              end
         
     | 
| 
       70 
81 
     | 
    
         | 
| 
       71 
     | 
    
         
            -
              def  
     | 
| 
       72 
     | 
    
         
            -
                 
     | 
| 
       73 
     | 
    
         
            -
                puts "CableReady #{CableReady::VERSION} update check skipped: pre-release build"  
     | 
| 
       74 
     | 
    
         
            -
                 
     | 
| 
      
 82 
     | 
    
         
            +
              def using_preview_release?
         
     | 
| 
      
 83 
     | 
    
         
            +
                preview = CableReady::VERSION.match?(LATEST_VERSION_FORMAT) == false
         
     | 
| 
      
 84 
     | 
    
         
            +
                puts "👉 CableReady #{CableReady::VERSION} update check skipped: pre-release build" if preview
         
     | 
| 
      
 85 
     | 
    
         
            +
                preview
         
     | 
| 
       75 
86 
     | 
    
         
             
              end
         
     | 
| 
       76 
87 
     | 
    
         | 
| 
       77 
88 
     | 
    
         
             
              def gem_version
         
     | 
| 
       78 
89 
     | 
    
         
             
                @_gem_version ||= CableReady::VERSION.gsub(".pre", "-pre")
         
     | 
| 
       79 
90 
     | 
    
         
             
              end
         
     | 
| 
       80 
91 
     | 
    
         | 
| 
       81 
     | 
    
         
            -
              def  
     | 
| 
       82 
     | 
    
         
            -
                @ 
     | 
| 
      
 92 
     | 
    
         
            +
              def npm_version
         
     | 
| 
      
 93 
     | 
    
         
            +
                @_npm_version ||= find_npm_version
         
     | 
| 
       83 
94 
     | 
    
         
             
              end
         
     | 
| 
       84 
95 
     | 
    
         | 
| 
       85 
     | 
    
         
            -
              def  
     | 
| 
      
 96 
     | 
    
         
            +
              def find_npm_version
         
     | 
| 
       86 
97 
     | 
    
         
             
                if (match = search_file(package_json_path, regex: /version/))
         
     | 
| 
       87 
98 
     | 
    
         
             
                  match[JSON_VERSION_FORMAT, 1]
         
     | 
| 
       88 
99 
     | 
    
         
             
                elsif (match = search_file(yarn_lock_path, regex: /^cable_ready/))
         
     | 
| 
         @@ -91,7 +102,7 @@ class CableReady::SanityChecker 
     | 
|
| 
       91 
102 
     | 
    
         
             
              end
         
     | 
| 
       92 
103 
     | 
    
         | 
| 
       93 
104 
     | 
    
         
             
              def search_file(path, regex:)
         
     | 
| 
       94 
     | 
    
         
            -
                return  
     | 
| 
      
 105 
     | 
    
         
            +
                return if File.exist?(path) == false
         
     | 
| 
       95 
106 
     | 
    
         
             
                File.foreach(path).grep(regex).first
         
     | 
| 
       96 
107 
     | 
    
         
             
              end
         
     | 
| 
       97 
108 
     | 
    
         | 
| 
         @@ -103,49 +114,38 @@ class CableReady::SanityChecker 
     | 
|
| 
       103 
114 
     | 
    
         
             
                Rails.root.join("yarn.lock")
         
     | 
| 
       104 
115 
     | 
    
         
             
              end
         
     | 
| 
       105 
116 
     | 
    
         | 
| 
       106 
     | 
    
         
            -
              def  
     | 
| 
       107 
     | 
    
         
            -
                 
     | 
| 
      
 117 
     | 
    
         
            +
              def initializer_missing?
         
     | 
| 
      
 118 
     | 
    
         
            +
                File.exist?(Rails.root.join("config", "initializers", "cable_ready.rb")) == false
         
     | 
| 
       108 
119 
     | 
    
         
             
              end
         
     | 
| 
       109 
120 
     | 
    
         | 
| 
       110 
121 
     | 
    
         
             
              def warn_and_exit(text)
         
     | 
| 
       111 
     | 
    
         
            -
                puts 
     | 
| 
      
 122 
     | 
    
         
            +
                puts
         
     | 
| 
      
 123 
     | 
    
         
            +
                puts "Heads up! 🔥"
         
     | 
| 
      
 124 
     | 
    
         
            +
                puts
         
     | 
| 
       112 
125 
     | 
    
         
             
                puts text
         
     | 
| 
       113 
     | 
    
         
            -
                exit_with_info if CableReady.config.on_failed_sanity_checks == :exit
         
     | 
| 
       114 
     | 
    
         
            -
              end
         
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
              def exit_with_info
         
     | 
| 
       117 
126 
     | 
    
         
             
                puts
         
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
                if File.exist?(initializer_path)
         
     | 
| 
      
 127 
     | 
    
         
            +
                if CableReady.config.on_failed_sanity_checks == :exit
         
     | 
| 
       120 
128 
     | 
    
         
             
                  puts <<~INFO
         
     | 
| 
       121 
     | 
    
         
            -
                     
     | 
| 
       122 
     | 
    
         
            -
                    you can add the following directive to the CableReady initializer,
         
     | 
| 
       123 
     | 
    
         
            -
                    which is located at #{initializer_path}
         
     | 
| 
      
 129 
     | 
    
         
            +
                    To ignore any warnings and start the application anyway, you can set the SKIP_SANITY_CHECK environment variable:
         
     | 
| 
       124 
130 
     | 
    
         | 
| 
       125 
     | 
    
         
            -
                       
     | 
| 
       126 
     | 
    
         
            -
                        config.on_failed_sanity_checks = :warn
         
     | 
| 
       127 
     | 
    
         
            -
                      end
         
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
     | 
    
         
            -
                  INFO
         
     | 
| 
       130 
     | 
    
         
            -
                else
         
     | 
| 
       131 
     | 
    
         
            -
                  puts <<~INFO
         
     | 
| 
       132 
     | 
    
         
            -
                    If you know what you are doing and you want to start the application anyway,
         
     | 
| 
       133 
     | 
    
         
            -
                    you can create a CableReady initializer with the command:
         
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
       135 
     | 
    
         
            -
                    bundle exec rails generate cable_ready:config
         
     | 
| 
      
 131 
     | 
    
         
            +
                      SKIP_SANITY_CHECK=true rails
         
     | 
| 
       136 
132 
     | 
    
         | 
| 
       137 
     | 
    
         
            -
                     
     | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
                    #{initializer_path}
         
     | 
| 
       140 
     | 
    
         
            -
             
     | 
| 
       141 
     | 
    
         
            -
                    and then add the following directive:
         
     | 
| 
      
 133 
     | 
    
         
            +
                    To do this permanently, add the following directive to the CableReady initializer:
         
     | 
| 
       142 
134 
     | 
    
         | 
| 
       143 
135 
     | 
    
         
             
                      CableReady.configure do |config|
         
     | 
| 
       144 
136 
     | 
    
         
             
                        config.on_failed_sanity_checks = :warn
         
     | 
| 
       145 
137 
     | 
    
         
             
                      end
         
     | 
| 
       146 
138 
     | 
    
         | 
| 
       147 
139 
     | 
    
         
             
                  INFO
         
     | 
| 
      
 140 
     | 
    
         
            +
                  if initializer_missing?
         
     | 
| 
      
 141 
     | 
    
         
            +
                    puts <<~INFO
         
     | 
| 
      
 142 
     | 
    
         
            +
                      You can create a CableReady initializer with the command:
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                        bundle exec rails generate cable_ready:initializer
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                    INFO
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
                  exit false if Rails.env.test? == false
         
     | 
| 
       148 
149 
     | 
    
         
             
                end
         
     | 
| 
       149 
     | 
    
         
            -
                exit false unless Rails.env.test?
         
     | 
| 
       150 
150 
     | 
    
         
             
              end
         
     | 
| 
       151 
151 
     | 
    
         
             
            end
         
     | 
    
        data/lib/cable_ready/version.rb
    CHANGED
    
    
    
        data/lib/cable_ready.rb
    CHANGED
    
    | 
         @@ -30,8 +30,11 @@ module CableReady 
     | 
|
| 
       30 
30 
     | 
    
         
             
                initializer "renderer" do
         
     | 
| 
       31 
31 
     | 
    
         
             
                  ActiveSupport.on_load(:action_controller) do
         
     | 
| 
       32 
32 
     | 
    
         
             
                    ActionController::Renderers.add :operations do |operations, options|
         
     | 
| 
      
 33 
     | 
    
         
            +
                      response.content_type ||= Mime[:cable_ready]
         
     | 
| 
       33 
34 
     | 
    
         
             
                      render json: operations.dispatch
         
     | 
| 
       34 
35 
     | 
    
         
             
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    Mime::Type.register "application/vnd.cable-ready.json", :cable_ready
         
     | 
| 
       35 
38 
     | 
    
         
             
                  end
         
     | 
| 
       36 
39 
     | 
    
         
             
                end
         
     | 
| 
       37 
40 
     | 
    
         
             
              end
         
     | 
| 
         @@ -4,7 +4,7 @@ require "rails/generators" 
     | 
|
| 
       4 
4 
     | 
    
         
             
            require "fileutils"
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            module CableReady
         
     | 
| 
       7 
     | 
    
         
            -
              class  
     | 
| 
      
 7 
     | 
    
         
            +
              class HelpersGenerator < Rails::Generators::Base
         
     | 
| 
       8 
8 
     | 
    
         
             
                desc "Initializes CableReady with a reference to the shared ActionCable consumer"
         
     | 
| 
       9 
9 
     | 
    
         
             
                source_root File.expand_path("templates", __dir__)
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class ApplicationJob < ActiveJob::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Automatically retry jobs that encountered a deadlock
         
     | 
| 
      
 3 
     | 
    
         
            +
              # retry_on ActiveRecord::Deadlocked
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              # Most jobs are safe to ignore if the underlying records are no longer available
         
     | 
| 
      
 6 
     | 
    
         
            +
              # discard_on ActiveJob::DeserializationError
         
     | 
| 
      
 7 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class GlobalIdableEntity
         
     | 
| 
      
 2 
     | 
    
         
            +
              include GlobalID::Identification
         
     | 
| 
      
 3 
     | 
    
         
            +
              include CableReady::Updatable
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              def id
         
     | 
| 
      
 6 
     | 
    
         
            +
                "fake-id"
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              def self.find(id)
         
     | 
| 
      
 10 
     | 
    
         
            +
                new if id == "fake-id"
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def fake_update
         
     | 
| 
      
 14 
     | 
    
         
            +
                ModelUpdatableCallbacks.new(:update).after_commit(self)
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "boot"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rails/all"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            # Require the gems listed in Gemfile, including any gems
         
     | 
| 
      
 6 
     | 
    
         
            +
            # you've limited to :test, :development, or :production.
         
     | 
| 
      
 7 
     | 
    
         
            +
            Bundler.require(*Rails.groups)
         
     | 
| 
      
 8 
     | 
    
         
            +
            require "cable_ready"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            module Dummy
         
     | 
| 
      
 11 
     | 
    
         
            +
              class Application < Rails::Application
         
     | 
| 
      
 12 
     | 
    
         
            +
                config.load_defaults Rails::VERSION::STRING.to_f
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                # Configuration for the application, engines, and railties goes here.
         
     | 
| 
      
 15 
     | 
    
         
            +
                #
         
     | 
| 
      
 16 
     | 
    
         
            +
                # These settings can be overridden in specific environments using the files
         
     | 
| 
      
 17 
     | 
    
         
            +
                # in config/environments, which are processed later.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # config.time_zone = "Central Time (US & Canada)"
         
     | 
| 
      
 20 
     | 
    
         
            +
                # config.eager_load_paths << Rails.root.join("extras")
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     |