diff --git a/Gemfile.lock b/Gemfile.lock index cc77bbc..cc3f721 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -167,6 +167,8 @@ GEM tzinfo (1.2.7) thread_safe (~> 0.1) unicode-display_width (1.7.0) + view_component (2.19.1) + activesupport (>= 5.0.0, < 7.0) websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -185,6 +187,7 @@ DEPENDENCIES spy sqlite3 standardrb + view_component BUNDLED WITH 2.1.4 diff --git a/futurism.gemspec b/futurism.gemspec index 5a92b3e..e1184cb 100644 --- a/futurism.gemspec +++ b/futurism.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "standardrb" spec.add_development_dependency "sqlite3" spec.add_development_dependency "spy" + spec.add_development_dependency "view_component" spec.add_dependency "rack", "~> 2.0" spec.add_dependency "rails", ">= 5.2" diff --git a/lib/futurism.rb b/lib/futurism.rb index b831d55..e515414 100644 --- a/lib/futurism.rb +++ b/lib/futurism.rb @@ -4,6 +4,7 @@ require "futurism/engine" require "futurism/channel" require "futurism/helpers" +require "futurism/shims/view_component_serializer" module Futurism extend ActiveSupport::Autoload diff --git a/lib/futurism/shims/view_component_serializer.rb b/lib/futurism/shims/view_component_serializer.rb new file mode 100644 index 0000000..5327968 --- /dev/null +++ b/lib/futurism/shims/view_component_serializer.rb @@ -0,0 +1,43 @@ +return unless defined?(ViewComponent) + +def serialize_value(value) + if value.is_a?(ActiveRecord::Base) && !value.new_record? + value.to_global_id.to_s + else + value + end +end + +def deep_serialize_array(array) + array.map do |value| + if value.is_a?(Hash) + require_relative "../shims/deep_transform_values" unless value.respond_to? :deep_transform_values + value.deep_transform_values { |val| serialize_value(val) } + else + serialize_value(value) + end + end +end + +module ViewComponent + module FuturismSerializer + def to_futurism_serialized + deep_serialize_array(@raw_initialization_arguments).to_json + end + end + + class Base + # Override base new method so that we can hack into the + # initialization process for each view component + # Ideally, a PR to the ViewComponent team would allow us to remove our implementation + # include FuturismSerializer + + def self.new(*arguments, &block) + instance = allocate + instance.singleton_class.include(FuturismSerializer) + instance.instance_variable_set("@raw_initialization_arguments", arguments) + instance.send(:initialize, *arguments, &block) + instance + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2b89b08..4cd6509 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,7 @@ # Configure Rails Environment ENV["RAILS_ENV"] = "test" +require "view_component" require_relative "../test/dummy/config/environment" require "rails/test_help" require "nokogiri" diff --git a/test/view_components/serializer_test.rb b/test/view_components/serializer_test.rb new file mode 100644 index 0000000..24000b5 --- /dev/null +++ b/test/view_components/serializer_test.rb @@ -0,0 +1,76 @@ +require "test_helper" + +class Futurism::Test < ActiveSupport::TestCase + test "component is a ViewComponent" do + assert_includes NoArgumentComponent.ancestors, ViewComponent::Base + assert_includes SimpleArgumentComponent.ancestors, ViewComponent::Base + assert_includes ComplexArgumentComponent.ancestors, ViewComponent::Base + end + + test "component instance responds to #to_futurism_serialized" do + instance = NoArgumentComponent.new + assert_respond_to instance, :to_futurism_serialized + end + + test "component instance saves no arguments" do + instance = NoArgumentComponent.new + + assert_respond_to instance, :to_futurism_serialized + assert_equal instance.instance_variable_get("@raw_initialization_arguments"), [] + assert_equal instance.to_futurism_serialized, [].to_json + end + + test "component instance saves simple arguments" do + instance = SimpleArgumentComponent.new(false, "/to/somewhere") + + assert_respond_to instance, :to_futurism_serialized + assert_equal instance.instance_variable_get("@raw_initialization_arguments"), [false, "/to/somewhere"] + assert_equal instance.to_futurism_serialized, [false, "/to/somewhere"].to_json + end + + test "component instance saves complex arguments" do + post = Post.create title: "Lorem" + instance = ComplexArgumentComponent.new(false, "/to/somewhere", deactivate: "Deactivate!", active: "Activate!", ar_object: post) + + assert_respond_to instance, :to_futurism_serialized + assert_equal instance.instance_variable_get("@raw_initialization_arguments"), [false, "/to/somewhere", deactivate: "Deactivate!", active: "Activate!", ar_object: post] + assert_equal instance.to_futurism_serialized, [false, "/to/somewhere", deactivate: "Deactivate!", active: "Activate!", ar_object: post.to_global_id.to_s].to_json + end +end + +class NoArgumentComponent < ViewComponent::Base + def call + link_to active? ? "Deactivate" : "Activate", "/to/somewhere" + end + + def active? + [true, false].sample + end +end + +class SimpleArgumentComponent < ViewComponent::Base + def initialize(active, path) + @active, @path = active, path + end + + def call + link_to active ? "Deactivate" : "Activate", path + end + + attr_reader :active, :path +end + +class ComplexArgumentComponent < ViewComponent::Base + def initialize(active, path, decativate: "Deactivate", activate: "Activate", **others) + @active, @path = active, path + @deactivate = decativate + @activate = activate + @others = others + end + + def call + link_to active ? "Deactivate" : "Activate", path + end + + attr_reader :active, :path, :deactivate, :activate, :others +end