From 9f3325b2de66a9133e6c497a92e13e569c9f0bad Mon Sep 17 00:00:00 2001 From: Ethan Barron Date: Tue, 18 Aug 2020 10:15:47 -0500 Subject: [PATCH 1/2] Attribute name overrides --- lib/optimism.rb | 117 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 30 deletions(-) diff --git a/lib/optimism.rb b/lib/optimism.rb index d508f00..b0413a8 100644 --- a/lib/optimism.rb +++ b/lib/optimism.rb @@ -1,45 +1,75 @@ -require "cable_ready" -require "optimism/version" -require "optimism/railtie" if defined?(Rails) +require 'cable_ready' +require 'optimism/version' +require 'optimism/railtie' if defined?(Rails) module Optimism include CableReady::Broadcaster + class << self - mattr_accessor :channel, :form_class, :error_class, :disable_submit, :suffix, :emit_events, :add_css, :inject_inline, :container_selector, :error_selector, :form_selector, :submit_selector - self.channel = ->(context) { "OptimismChannel" } - self.form_class = "invalid" - self.error_class = "error" + mattr_accessor :add_css, :channel, :container_selector, :disable_submit, + :emit_events, :error_class, :error_selector, :form_class, :form_selector, + :inject_inline, :submit_selector,:suffix + + self.add_css = true + self.channel = ->(context) { 'OptimismChannel' } + self.container_selector = '#RESOURCE_ATTRIBUTE_container' self.disable_submit = false - self.suffix = "" self.emit_events = false - self.add_css = true + self.error_class = 'error' + self.error_selector = '#RESOURCE_ATTRIBUTE_error' + self.form_class = 'invalid' + self.form_selector = '#RESOURCE_form' self.inject_inline = true - self.container_selector = "#RESOURCE_ATTRIBUTE_container" - self.error_selector = "#RESOURCE_ATTRIBUTE_error" - self.form_selector = "#RESOURCE_form" - self.submit_selector = "#RESOURCE_submit" - end + self.submit_selector = '#RESOURCE_submit' + self.suffix = '' + + def configure(&block) + yield self + end - def self.configure(&block) - yield self + private + + def _id_for(element, resource, attribute = nil) + _selector_for(element, resource, attribute)[1..-1] + end + + def _selector_for(element, resource, attribute = nil) + selector = send("#{element}_selector").dup + selector.sub!('RESOURCE', resource.to_s) if selector.include?('RESOURCE') + selector.sub!('ATTRIBUTE', attribute.to_s) if selector.include?('ATTRIBUTE') + selector + end + + def _resource_from_object_name(object_name) + object_name.to_s.delete("]").tr("[", "_") + end end - def broadcast_errors(model, attributes) + def broadcast_errors(model, attributes, attribute_names = {}) return unless model&.errors&.messages + resource = ActiveModel::Naming.param_key(model) - form_selector, submit_selector = Optimism.form_selector.sub("RESOURCE", resource), Optimism.submit_selector.sub("RESOURCE", resource) + form_selector = Optimism.send(:_selector_for, :form, resource) + submit_selector = Optimism.send(:_selector_for, :submit, resource) + attributes = case attributes when ActionController::Parameters, Hash, ActiveSupport::HashWithIndifferentAccess attributes.to_h when String, Symbol - { attributes.to_s => nil } + { attributes => nil } when Array - attributes.flatten.each.with_object({}) { |attr, obj| obj[attr.to_s] = nil } + attributes.flatten.each.with_object({}) { |attr, obj| obj[attr] = nil } else raise Exception.new "attributes must be a Hash (Parameters, Indifferent or standard), Array, Symbol or String" end + + attributes = ActiveSupport::HashWithIndifferentAccess.new(attributes) + attribute_names = ActiveSupport::HashWithIndifferentAccess.new(attribute_names) + model.valid? if model.errors.empty? - process_resource(model, attributes, [resource]) + + process_resource(model, attributes, attribute_names, [resource]) + if model.errors.any? cable_ready[Optimism.channel[self]].dispatch_event(name: "optimism:form:invalid", detail: {resource: resource}) if Optimism.emit_events cable_ready[Optimism.channel[self]].add_css_class(selector: form_selector, name: Optimism.form_class) if Optimism.form_class.present? @@ -49,38 +79,55 @@ def broadcast_errors(model, attributes) cable_ready[Optimism.channel[self]].remove_css_class(selector: form_selector, name: Optimism.form_class) if Optimism.form_class.present? cable_ready[Optimism.channel[self]].remove_attribute(selector: submit_selector, name: "disabled") if Optimism.disable_submit end + cable_ready.broadcast + head :ok if defined?(head) end - def process_resource(model, attributes, ancestry) + def process_resource(model, attributes, attribute_names, ancestry) + attribute_names ||= ActiveSupport::HashWithIndifferentAccess.new({}) + attributes.keys.each do |attribute| if attribute.ends_with?("_attributes") resource = attribute[0..-12] association = model.send(resource.to_sym) if association.respond_to? :each_with_index association.each_with_index do |nested, index| - process_resource(nested, attributes[attribute][index.to_s], ancestry + [resource, index]) if attributes[attribute].key?(index.to_s) + process_resource(nested, attributes[attribute][index.to_s], attribute_names[attribute], ancestry + [resource, index]) if attributes[attribute].key?(index.to_s) end else - process_resource(association, attributes[attribute], ancestry + [resource]) + process_resource(association, attributes[attribute], attribute_names[attribute], ancestry + [resource]) end else - process_attribute(model, attribute, ancestry.dup) + process_attribute(model, attribute, attribute_names[attribute], ancestry.dup) end end end - def process_attribute(model, attribute, ancestry) + def process_attribute(model, attribute, attribute_name, ancestry) resource = ancestry.shift + if ancestry.size == 1 resource += "_#{ancestry.shift}_attributes" else resource += "_#{ancestry.shift}_attributes_#{ancestry.shift}" until ancestry.empty? end - container_selector, error_selector = Optimism.container_selector.sub("RESOURCE", resource).sub("ATTRIBUTE", attribute), Optimism.error_selector.sub("RESOURCE", resource).sub("ATTRIBUTE", attribute) + + container_selector = Optimism.send(:_selector_for, :container, resource, attribute) + error_selector = Optimism.send(:_selector_for, :error, resource, attribute) + if model.errors.any? && model.errors.messages.map(&:first).include?(attribute.to_sym) - message = "#{model.errors.full_message(attribute.to_sym, model.errors.messages[attribute.to_sym].first)}#{Optimism.suffix}" + if attribute_name == false + attr_in_msg = :base + elsif attribute_name + attr_in_msg = attribute_name + else + attr_in_msg = attribute.to_sym + end + + message = "#{model.errors.full_message(attr_in_msg, model.errors.messages[attribute.to_sym].first)}#{Optimism.suffix}" + cable_ready[Optimism.channel[self]].dispatch_event(name: "optimism:attribute:invalid", detail: {resource: resource, attribute: attribute, text: message}) if Optimism.emit_events cable_ready[Optimism.channel[self]].add_css_class(selector: container_selector, name: Optimism.error_class) if Optimism.add_css cable_ready[Optimism.channel[self]].text_content(selector: error_selector, text: message) if Optimism.inject_inline @@ -99,7 +146,12 @@ def container_for(attribute, **options, &block) end def container_id_for(attribute) - Optimism.container_selector.sub("RESOURCE", object_name.to_s.delete("]").tr("[", "_")).sub("ATTRIBUTE", attribute.to_s)[1..-1] + Optimism.send( + :_id_for, + :container, + Optimism.send(:_resource_from_object_name, object_name), + attribute + ) end def error_for(attribute, **options) @@ -107,7 +159,12 @@ def error_for(attribute, **options) end def error_id_for(attribute) - Optimism.error_selector.sub("RESOURCE", object_name.to_s.delete("]").tr("[", "_")).sub("ATTRIBUTE", attribute.to_s)[1..-1] + Optimism.send( + :_id_for, + :error, + Optimism.send(:_resource_from_object_name, object_name), + attribute + ) end end end From d48b52df0f91283faeb82e995abeed67745ba731 Mon Sep 17 00:00:00 2001 From: Ethan Barron Date: Tue, 18 Aug 2020 10:46:40 -0500 Subject: [PATCH 2/2] Circumvent #full_message entirely for attribute name overrides --- lib/optimism.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/optimism.rb b/lib/optimism.rb index b0413a8..0460ac0 100644 --- a/lib/optimism.rb +++ b/lib/optimism.rb @@ -118,15 +118,15 @@ def process_attribute(model, attribute, attribute_name, ancestry) error_selector = Optimism.send(:_selector_for, :error, resource, attribute) if model.errors.any? && model.errors.messages.map(&:first).include?(attribute.to_sym) - if attribute_name == false - attr_in_msg = :base - elsif attribute_name - attr_in_msg = attribute_name + err_msg = model.errors.messages[attribute.to_sym].first + if attribute_name + message = "#{attribute_name} #{err_msg}" + elsif attribute_name == false + message = err_msg else - attr_in_msg = attribute.to_sym + message = model.errors.full_message(attribute.to_sym, err_msg) end - - message = "#{model.errors.full_message(attr_in_msg, model.errors.messages[attribute.to_sym].first)}#{Optimism.suffix}" + message.squish! cable_ready[Optimism.channel[self]].dispatch_event(name: "optimism:attribute:invalid", detail: {resource: resource, attribute: attribute, text: message}) if Optimism.emit_events cable_ready[Optimism.channel[self]].add_css_class(selector: container_selector, name: Optimism.error_class) if Optimism.add_css