hotwired / stimulus-rails

Use Stimulus in your Ruby on Rails app
https://stimulus.hotwired.dev
MIT License
637 stars 91 forks source link

ActiveModel not working with value to Object #124

Closed dmlond closed 1 year ago

dmlond commented 1 year ago

I am using stimulus-rails version 1.2.1

I have 2 Plain Objects that include ActiveModel::Serializers::JSON.

class Switcher
  include ActiveModel::Serializers::JSON
  attr_reader :object, :name, :labels, :host, :managed_deployments

    def initialize(object)
      @object = object
      @name = object['metadata']['name']
      @labels = object['metadata']['labels']
      @host = object['spec']['host']
      @managed_deployments = []
    end

    def manages?(deployment)
      return false unless deployment.labels["oasis.duke.edu/#{@name}-managed"] == "true"

      @managed_deployments << deployment
      return true
    end

    def attributes
      {'name' => nil, 'host' => nil, 'managed_deployments' => nil}
    end
end

class Deployment
  include ActiveModel::Serializers::JSON

  attr_reader :object, :name, :labels

  def initialize(object)
    @object = object
    @name = object['metadata']['name']
    @labels = object['metadata']['labels']
  end

  def attributes
    {'name' => nil}
  end
end

I would like to pass an instance variable @switchers that contains an Array of Switcher objects into a Stimulus Controller as a value of type Object.

import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
  static values = {
    switchers: Object
  }

  connect() {
    console.log("Connected!");
    console.log(this.switchersValue[0).name);
  }
}

I have tried the following in my views

<div id="blockly_container" 
    data-controller="blockly"
    data-blockly-switchers-value=<%= @switchers -%>
>

This ignores the ActiveModel::Serializers::JSON entirely and renders the entire object with its class name, which causes a JSON.parse error when the stimulus controller is loaded.

console output (truncated)

Connected! blockly_controller-7a40db76079b110b2667415e065bcec0d6ee8711eb74cd36f1c3854b0d506a2a.js:13:13
Error connecting controller

 SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data
...

the DOM looks like this

<div id="blockly_container" data-controller="blockly" data-blockly-switchers-value="[#<Oasis::Switcher:0x00007f80f9aa9930" @object="#<K8s::Resource" metadata="{:name=>&quot;test-switcher-one&quot;," :namespace=">&quot;londo003&quot;," :selflink=">&quot;/apis/route.openshift.io/v1/namespaces/londo003/routes/test-switcher-one&quot;," :uid=">&quot;f785fca6-2c87-11ee-8fee-005056b47ad1&quot;," :resourceversion=">&quot;907690199&quot;," :creationtimestamp=">&quot;2023-07-27T14:14:58Z&quot;," :labels=">{:&quot;oasis.duke.edu/ingress-switcher&quot;=>&quot;true&quot;," :&quot;oasis.duke.edu="" ingress-switcher-name&quot;=">&quot;test-switcher-one&quot;}," :annotations=">{:&quot;openshift.io/host.generated&quot;=>&quot;true&quot;}}," spec="{:host=>&quot;test-switcher-one-londo003.ocp.dhe.duke.edu&quot;," :to=">{:kind=>&quot;Service&quot;," :name=">&quot;test-switcher-one&quot;," :weight=">100}," :port=">{:targetPort=>&quot;http&quot;}," :tls=">{:termination=>&quot;edge&quot;," :insecureedgeterminationpolicy=">&quot;Redirect&quot;}," :wildcardpolicy=">&quot;None&quot;}," status="{:ingress=>[{:host=>&quot;test-switcher-one-londo003.ocp.dhe.duke.edu&quot;," :routername=">&quot;router&quot;," :conditions=">[{:type=>&quot;Admitted&quot;," :status=">&quot;True&quot;," :lasttransitiontime=">&quot;2023-07-27T14:14:58Z&quot;}]," apiversion="&quot;route.openshift.io/v1&quot;," kind="&quot;Route&quot;>," @name="&quot;test-switcher-one&quot;," @labels="#<K8s::Resource" oasis.duke.edu="" ingress-switcher="&quot;true&quot;," ingress-switcher-name="&quot;test-switcher-one&quot;>," @host="&quot;test-switcher-one-londo003.ocp.dhe.duke.edu&quot;," @managed_deployments="[#<Oasis::SwitchableDeployment:0x00007f80f9a8b390" :generation=">1," :environment=">&quot;review-a&quot;," ingress-switcher-managed&quot;=">&quot;true&quot;," test-switcher-one-managed&quot;=">&quot;true&quot;}," :selector=">{:matchLabels=>{:app=>&quot;whoami&quot;," :template=">{:metadata=>{:creationTimestamp=>nil," :spec=">{:containers=>[{:name=>&quot;whoami&quot;," :image=">&quot;traefik/whoami&quot;," :ports=">[{:containerPort=>8080," :protocol=">&quot;TCP&quot;}]," :env=">[{:name=>&quot;WHOAMI_PORT_NUMBER&quot;," :value=">&quot;8080&quot;}]," :resources=">{}," :terminationmessagepath=">&quot;/dev/termination-log&quot;," :terminationmessagepolicy=">&quot;File&quot;," :imagepullpolicy=">&quot;Always&quot;}]," :restartpolicy=">&quot;Always&quot;," :terminationgraceperiodseconds=">30," :dnspolicy=">&quot;ClusterFirst&quot;," :securitycontext=">{}," :schedulername=">&quot;default-scheduler&quot;}}," :strategy=">{:type=>&quot;RollingUpdate&quot;," :rollingupdate=">{:maxUnavailable=>&quot;25%&quot;," :maxsurge=">&quot;25%&quot;}}," :revisionhistorylimit=">10," :progressdeadlineseconds=">600}," :replicas=">1," :updatedreplicas=">1," :readyreplicas=">1," :availablereplicas=">1," :lastupdatetime=">&quot;2023-07-27T14:15:31Z&quot;," :reason=">&quot;MinimumReplicasAvailable&quot;," :message=">&quot;Deployment" has="" minimum="" availability.&quot;},="" {:type=">&quot;Progressing&quot;," \&quot;whoami-review-a-68848b457\&quot;="" successfully="" progressed.&quot;}]},="" app="&quot;whoami&quot;," environment="&quot;review-a&quot;," ingress-switcher-managed="&quot;true&quot;," test-switcher-one-managed="&quot;true&quot;>>]>," #&lt;oasis::switcher:0x00007f80f9aa9430="">
...
</div>

I tried to use to_json

<div id="blockly_container" 
    data-controller="blockly"
    data-blockly-switchers-value=<%= @switchers.to_json -%>
>

This results in the following error in the console

Connected! blockly_controller-7a40db76079b110b2667415e065bcec0d6ee8711eb74cd36f1c3854b0d506a2a.js:13:13
Error connecting controller

 TypeError: expected value of type "object" but instead got value "[{"name":"test-switcher-one","host":"test-switcher-one-londo003.ocp.dhe.duke.edu","managed_deployments":[{"name":"whoami-review-a"}]},{"name":"test-switcher-two","host":"test-switcher-two-londo003.ocp.dhe.duke.edu","managed_deployments":[]}]" of type "array"

The rendered DOM element looks ok to me, except some html escapes that are sent in

<div id="blockly_container" data-controller="blockly" data-blockly-switchers-value="[{&quot;name&quot;:&quot;test-switcher-one&quot;,&quot;host&quot;:&quot;test-switcher-one-londo003.ocp.dhe.duke.edu&quot;,&quot;managed_deployments&quot;:[{&quot;name&quot;:&quot;whoami-review-a&quot;}]},{&quot;name&quot;:&quot;test-switcher-two&quot;,&quot;host&quot;:&quot;test-switcher-two-londo003.ocp.dhe.duke.edu&quot;,&quot;managed_deployments&quot;:[]}]">
...
</div>

But if I change the value type to String, and use JSON.parse on the value, it gives me the expected object.

import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
  static values = {
    switchers: String
  }

  connect() {
    console.log("Connected!");
    console.log(JSON.parse(this.switchersValue)[0).name);
  }
}
<div id="blockly_container" 
    data-controller="blockly"
    data-blockly-switchers-value=<%= @switchers.to_json -%>
>

What is the expected behavior?

dhh commented 1 year ago

Believe you need to call to_json.html_safe when setting the value. Either way, all of this is usage questions, not a bug. Please use the Hotwire forum or Discord for usage help.

dmlond commented 1 year ago

it seems like a bug to me. The handbook states that the 'Object' type is decoded as JSON.parse(value). But the value I am passing into the value of type String can be turned into an object by passing it through JSON.parse(value), but the same value does not work with a value of type Object. This is true with or without html_safe.

https://stimulus.hotwired.dev/reference/values#types